Merge changes Ie2944544,Ia7cec977,Id4e5ff33,Iad082ca5 into main
* changes:
Add support for interruptions in shared values
Move the implementation of AnimatedState into AnimatedStateImpl
Make animated values support multiple transitions
Expose the current scene key in SceneScope
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
index 459c286..515ddc8 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java
@@ -19,16 +19,13 @@
import android.os.Bundle;
import android.os.SystemClock;
import android.perftests.utils.ShellHelper;
-import android.util.Log;
import java.util.ArrayList;
// Based on //platform/frameworks/base/apct-tests/perftests/utils/BenchmarkState.java
public class BenchmarkRunner {
- private static final String TAG = BenchmarkRunner.class.getSimpleName();
+
private static final long COOL_OFF_PERIOD_MS = 1000;
- private static final int CPU_IDLE_TIMEOUT_MS = 60 * 1000;
- private static final int CPU_IDLE_THRESHOLD_PERCENTAGE = 90;
private static final int NUM_ITERATIONS = 4;
@@ -83,7 +80,7 @@
private void prepareForNextRun() {
SystemClock.sleep(COOL_OFF_PERIOD_MS);
- waitCoolDownPeriod();
+ ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
mStartTimeNs = System.nanoTime();
mPausedDurationNs = 0;
}
@@ -105,7 +102,7 @@
* to avoid unnecessary waiting.
*/
public void resumeTiming() {
- waitCoolDownPeriod();
+ ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
resumeTimer();
}
@@ -165,56 +162,4 @@
}
return null;
}
-
- /** Waits for the broadcast queue and the CPU cores to be idle. */
- public void waitCoolDownPeriod() {
- waitForBroadcastIdle();
- waitForCpuIdle();
- }
-
- private void waitForBroadcastIdle() {
- Log.d(TAG, "starting to waitForBroadcastIdle");
- final long startedAt = System.currentTimeMillis();
- ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers");
- final long elapsed = System.currentTimeMillis() - startedAt;
- Log.d(TAG, "waitForBroadcastIdle is complete in " + elapsed + " ms");
- }
- private void waitForCpuIdle() {
- Log.d(TAG, "starting to waitForCpuIdle");
- final long startedAt = System.currentTimeMillis();
- while (true) {
- final int idleCpuPercentage = getIdleCpuPercentage();
- final long elapsed = System.currentTimeMillis() - startedAt;
- Log.d(TAG, "waitForCpuIdle " + idleCpuPercentage + "% (" + elapsed + "ms elapsed)");
- if (idleCpuPercentage >= CPU_IDLE_THRESHOLD_PERCENTAGE) {
- Log.d(TAG, "waitForCpuIdle is complete in " + elapsed + " ms");
- return;
- }
- if (elapsed >= CPU_IDLE_TIMEOUT_MS) {
- Log.e(TAG, "Ending waitForCpuIdle because it didn't finish in "
- + CPU_IDLE_TIMEOUT_MS + " ms");
- return;
- }
- SystemClock.sleep(1000);
- }
- }
-
- private int getIdleCpuPercentage() {
- String output = ShellHelper.runShellCommand("top -m 1 -n 1");
- String[] tokens = output.split("\\s+");
- float totalCpu = -1;
- float idleCpu = -1;
- for (String token : tokens) {
- if (token.contains("%cpu")) {
- totalCpu = Float.parseFloat(token.split("%")[0]);
- } else if (token.contains("%idle")) {
- idleCpu = Float.parseFloat(token.split("%")[0]);
- }
- }
- if (totalCpu < 0 || idleCpu < 0) {
- Log.e(TAG, "Could not get idle cpu percentage, output=" + output);
- return -1;
- }
- return (int) (100 * idleCpu / totalCpu);
- }
}
\ No newline at end of file
diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
index 98ab0c2..762e2af 100644
--- a/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
+++ b/apct-tests/perftests/multiuser/src/android/multiuser/UserLifecycleTests.java
@@ -188,6 +188,21 @@
}
}
+ /** Tests creating a new user, with wait times between iterations. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void createUser_realistic() throws RemoteException {
+ while (mRunner.keepRunning()) {
+ Log.i(TAG, "Starting timer");
+ final int userId = createUserNoFlags();
+
+ mRunner.pauseTiming();
+ Log.i(TAG, "Stopping timer");
+ removeUser(userId);
+ waitCoolDownPeriod();
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/** Tests creating and starting a new user. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void createAndStartUser() throws RemoteException {
@@ -224,6 +239,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -238,6 +254,7 @@
mRunner.pauseTiming();
final int userId = createUserNoFlags();
+ waitForBroadcastIdle();
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -292,6 +309,9 @@
preStartUser(userId, numberOfIterationsToSkip);
+ waitForBroadcastIdle();
+ waitCoolDownPeriod();
+
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -333,6 +353,9 @@
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+ waitForBroadcastIdle();
+ waitCoolDownPeriod();
+
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -397,6 +420,7 @@
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+ waitCoolDownPeriod();
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -430,6 +454,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -441,7 +466,6 @@
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
final int userId = createUserNoFlags();
-
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -455,6 +479,27 @@
}
}
+ /** Tests switching to an uninitialized user with wait times between iterations. */
+ @Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
+ public void switchUser_realistic() throws Exception {
+ while (mRunner.keepRunning()) {
+ mRunner.pauseTiming();
+ final int startUser = ActivityManager.getCurrentUser();
+ final int userId = createUserNoFlags();
+ waitCoolDownPeriod();
+ Log.d(TAG, "Starting timer");
+ mRunner.resumeTiming();
+
+ switchUser(userId);
+
+ mRunner.pauseTiming();
+ Log.d(TAG, "Stopping timer");
+ switchUserNoCheck(startUser);
+ removeUser(userId);
+ mRunner.resumeTimingForNextIteration();
+ }
+ }
+
/** Tests switching to a previously-started, but no-longer-running, user. */
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void switchUser_stopped() throws RemoteException {
@@ -462,7 +507,6 @@
mRunner.pauseTiming();
final int startUser = mAm.getCurrentUser();
final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true);
-
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -492,6 +536,7 @@
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+ waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -517,6 +562,7 @@
/* useStaticWallpaper */true);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+ waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -560,6 +606,7 @@
final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+ waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -567,6 +614,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
+ waitForBroadcastIdle();
switchUserNoCheck(startUser);
mRunner.resumeTimingForNextIteration();
}
@@ -583,6 +631,7 @@
/* useStaticWallpaper */ true);
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
+ waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -590,6 +639,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
+ waitForBroadcastIdle();
switchUserNoCheck(startUser);
mRunner.resumeTimingForNextIteration();
}
@@ -625,11 +675,13 @@
@Test(timeout = TIMEOUT_MAX_TEST_TIME_MS)
public void stopUser_realistic() throws RemoteException {
final int userId = createUserNoFlags();
+ waitCoolDownPeriod();
while (mRunner.keepRunning()) {
mRunner.pauseTiming();
runThenWaitForBroadcasts(userId, ()-> {
mIam.startUserInBackground(userId);
}, Intent.ACTION_USER_STARTED, Intent.ACTION_MEDIA_MOUNTED);
+ waitCoolDownPeriod();
Log.d(TAG, "Starting timer");
mRunner.resumeTiming();
@@ -651,7 +703,7 @@
final int startUser = mAm.getCurrentUser();
final int userId = createUserNoFlags();
- mRunner.waitCoolDownPeriod();
+ waitForBroadcastIdle();
mUserSwitchWaiter.runThenWaitUntilBootCompleted(userId, () -> {
mRunner.resumeTiming();
Log.i(TAG, "Starting timer");
@@ -674,7 +726,7 @@
final int startUser = ActivityManager.getCurrentUser();
final int userId = createUserNoFlags();
- mRunner.waitCoolDownPeriod();
+ waitCoolDownPeriod();
mUserSwitchWaiter.runThenWaitUntilBootCompleted(userId, () -> {
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
@@ -700,7 +752,7 @@
switchUser(userId);
}, Intent.ACTION_MEDIA_MOUNTED);
- mRunner.waitCoolDownPeriod();
+ waitForBroadcastIdle();
mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> {
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
@@ -729,7 +781,7 @@
switchUser(userId);
}, Intent.ACTION_MEDIA_MOUNTED);
- mRunner.waitCoolDownPeriod();
+ waitCoolDownPeriod();
mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> {
runThenWaitForBroadcasts(userId, () -> {
mRunner.resumeTiming();
@@ -775,6 +827,7 @@
Log.d(TAG, "Stopping timer");
attestTrue("Failed creating profile " + userId, mUm.isManagedProfile(userId));
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -815,6 +868,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -859,6 +913,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
removeUser(userId);
@@ -910,6 +965,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -974,6 +1030,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -1014,6 +1071,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -1066,6 +1124,7 @@
mRunner.pauseTiming();
Log.d(TAG, "Stopping timer");
removeUser(userId);
+ waitCoolDownPeriod();
mRunner.resumeTimingForNextIteration();
}
}
@@ -1105,6 +1164,7 @@
runThenWaitForBroadcasts(userId, () -> {
startUserInBackgroundAndWaitForUnlock(userId);
}, Intent.ACTION_MEDIA_MOUNTED);
+ waitCoolDownPeriod();
mRunner.resumeTiming();
Log.d(TAG, "Starting timer");
@@ -1220,7 +1280,6 @@
* If lack of success should fail the test, use {@link #switchUser(int)} instead.
*/
private boolean switchUserNoCheck(int userId) throws RemoteException {
- mRunner.waitCoolDownPeriod();
final boolean[] success = {true};
mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(userId, () -> {
mAm.switchUser(userId);
@@ -1237,7 +1296,7 @@
*/
private void stopUserAfterWaitingForBroadcastIdle(int userId)
throws RemoteException {
- mRunner.waitCoolDownPeriod();
+ waitForBroadcastIdle();
stopUser(userId);
}
@@ -1379,8 +1438,6 @@
*/
private void runThenWaitForBroadcasts(int userId, FunctionalUtils.ThrowingRunnable runnable,
String... actions) {
- mRunner.waitCoolDownPeriod();
-
final String unreceivedAction =
mBroadcastWaiter.runThenWaitForBroadcasts(userId, runnable, actions);
@@ -1481,4 +1538,28 @@
assertEquals("", ShellHelper.runShellCommand("setprop " + name + " " + value));
return TextUtils.firstNotEmpty(oldValue, "invalid");
}
+
+ private void waitForBroadcastIdle() {
+ try {
+ ShellHelper.runShellCommandWithTimeout(
+ "am wait-for-broadcast-idle --flush-broadcast-loopers", TIMEOUT_IN_SECOND);
+ } catch (TimeoutException e) {
+ Log.e(TAG, "Ending waitForBroadcastIdle because it is taking too long", e);
+ }
+ }
+
+ private void sleep(long ms) {
+ try {
+ Thread.sleep(ms);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+
+ private void waitCoolDownPeriod() {
+ // Heuristic value based on local tests. Stability increased compared to no waiting.
+ final int tenSeconds = 1000 * 10;
+ waitForBroadcastIdle();
+ sleep(tenSeconds);
+ }
}
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 96315eb..50d97cf 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -14435,6 +14435,7 @@
method @NonNull public android.telephony.CarrierRestrictionRules build();
method @NonNull public android.telephony.CarrierRestrictionRules.Builder setAllCarriersAllowed();
method @NonNull public android.telephony.CarrierRestrictionRules.Builder setAllowedCarriers(@NonNull java.util.List<android.service.carrier.CarrierIdentifier>);
+ method @FlaggedApi("com.android.internal.telephony.flags.set_carrier_restriction_status") @NonNull public android.telephony.CarrierRestrictionRules.Builder setCarrierRestrictionStatus(int);
method @NonNull public android.telephony.CarrierRestrictionRules.Builder setDefaultCarrierRestriction(int);
method @NonNull public android.telephony.CarrierRestrictionRules.Builder setExcludedCarriers(@NonNull java.util.List<android.service.carrier.CarrierIdentifier>);
method @NonNull public android.telephony.CarrierRestrictionRules.Builder setMultiSimPolicy(int);
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index fd9600c..65628d3 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -16,6 +16,7 @@
package android.accessibilityservice;
+import static android.accessibilityservice.AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION;
import static android.accessibilityservice.MagnificationConfig.MAGNIFICATION_MODE_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
@@ -69,6 +70,8 @@
import android.view.accessibility.AccessibilityWindowInfo;
import android.view.inputmethod.EditorInfo;
+import androidx.annotation.GuardedBy;
+
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.inputmethod.CancellationGroup;
import com.android.internal.inputmethod.IAccessibilityInputMethodSession;
@@ -640,6 +643,8 @@
/** The detected gesture information for different displays */
boolean onGesture(AccessibilityGestureEvent gestureInfo);
boolean onKeyEvent(KeyEvent event);
+ /** Magnification SystemUI connection changed callbacks */
+ void onMagnificationSystemUIConnectionChanged(boolean connected);
/** Magnification changed callbacks for different displays */
void onMagnificationChanged(int displayId, @NonNull Region region,
MagnificationConfig config);
@@ -790,7 +795,6 @@
public static final String KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP =
"screenshot_timestamp";
-
/**
* Annotations for result codes of attaching accessibility overlays.
*
@@ -837,6 +841,13 @@
private WindowManager mWindowManager;
+ @GuardedBy("mLock")
+ private boolean mServiceConnected;
+ @GuardedBy("mLock")
+ private boolean mMagnificationSystemUIConnected;
+ @GuardedBy("mLock")
+ private boolean mServiceConnectedNotified;
+
/** List of magnification controllers, mapping from displayId -> MagnificationController. */
private final SparseArray<MagnificationController> mMagnificationControllers =
new SparseArray<>(0);
@@ -886,11 +897,14 @@
for (int i = 0; i < mMagnificationControllers.size(); i++) {
mMagnificationControllers.valueAt(i).onServiceConnectedLocked();
}
+ checkIsMagnificationSystemUIConnectedAlready();
final AccessibilityServiceInfo info = getServiceInfo();
if (info != null) {
updateInputMethod(info);
mMotionEventSources = info.getMotionEventSources();
}
+ mServiceConnected = true;
+ mServiceConnectedNotified = false;
}
if (mSoftKeyboardController != null) {
mSoftKeyboardController.onServiceConnected();
@@ -898,7 +912,57 @@
// The client gets to handle service connection last, after we've set
// up any state upon which their code may rely.
- onServiceConnected();
+ if (android.view.accessibility.Flags
+ .waitMagnificationSystemUiConnectionToNotifyServiceConnected()) {
+ notifyOnServiceConnectedIfReady();
+ } else {
+ onServiceConnected();
+ }
+ }
+
+ private void notifyOnServiceConnectedIfReady() {
+ synchronized (mLock) {
+ if (mServiceConnectedNotified) {
+ return;
+ }
+ boolean canControlMagnification;
+ final AccessibilityServiceInfo info = getServiceInfo();
+ if (info != null) {
+ int flagMask = CAPABILITY_CAN_CONTROL_MAGNIFICATION;
+ canControlMagnification = (info.getCapabilities() & flagMask) == flagMask;
+ } else {
+ canControlMagnification = false;
+ }
+ boolean ready = canControlMagnification
+ ? (mServiceConnected && mMagnificationSystemUIConnected)
+ : mServiceConnected;
+ if (ready) {
+ getMainExecutor().execute(() -> onServiceConnected());
+ mServiceConnectedNotified = true;
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void checkIsMagnificationSystemUIConnectedAlready() {
+ if (!android.view.accessibility.Flags
+ .waitMagnificationSystemUiConnectionToNotifyServiceConnected()) {
+ return;
+ }
+ if (mMagnificationSystemUIConnected) {
+ return;
+ }
+ final IAccessibilityServiceConnection connection =
+ AccessibilityInteractionClient.getInstance(this).getConnection(mConnectionId);
+ if (connection != null) {
+ try {
+ boolean connected = connection.isMagnificationSystemUIConnected();
+ mMagnificationSystemUIConnected = connected;
+ } catch (RemoteException re) {
+ Log.w(LOG_TAG, "Failed to check magnification system ui connection", re);
+ re.rethrowFromSystemServer();
+ }
+ }
}
private void updateInputMethod(AccessibilityServiceInfo info) {
@@ -1360,6 +1424,22 @@
}
}
+ private void onMagnificationSystemUIConnectionChanged(boolean connected) {
+ if (!android.view.accessibility.Flags
+ .waitMagnificationSystemUiConnectionToNotifyServiceConnected()) {
+ return;
+ }
+
+ synchronized (mLock) {
+ boolean changed = (mMagnificationSystemUIConnected != connected);
+ mMagnificationSystemUIConnected = connected;
+
+ if (changed) {
+ notifyOnServiceConnectedIfReady();
+ }
+ }
+ }
+
private void onMagnificationChanged(int displayId, @NonNull Region region,
MagnificationConfig config) {
MagnificationController controller;
@@ -2823,6 +2903,11 @@
}
@Override
+ public void onMagnificationSystemUIConnectionChanged(boolean connected) {
+ AccessibilityService.this.onMagnificationSystemUIConnectionChanged(connected);
+ }
+
+ @Override
public void onMagnificationChanged(int displayId, @NonNull Region region,
MagnificationConfig config) {
AccessibilityService.this.onMagnificationChanged(displayId, region, config);
@@ -3032,6 +3117,16 @@
});
}
+ @Override
+ public void onMagnificationSystemUIConnectionChanged(boolean connected) {
+ mExecutor.execute(() -> {
+ if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+ mCallback.onMagnificationSystemUIConnectionChanged(connected);
+ }
+ return;
+ });
+ }
+
/** Magnification changed callbacks for different displays */
public void onMagnificationChanged(int displayId, @NonNull Region region,
MagnificationConfig config) {
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
index 3bc61e5..f1479ef 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl
@@ -48,6 +48,8 @@
void onKeyEvent(in KeyEvent event, int sequence);
+ void onMagnificationSystemUIConnectionChanged(boolean connected);
+
void onMagnificationChanged(int displayId, in Region region, in MagnificationConfig config);
void onMotionEvent(in MotionEvent event);
diff --git a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
index 713d8e5..149e719 100644
--- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
+++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl
@@ -130,6 +130,9 @@
void setMagnificationCallbackEnabled(int displayId, boolean enabled);
@RequiresNoPermission
+ boolean isMagnificationSystemUIConnected();
+
+ @RequiresNoPermission
boolean setSoftKeyboardShowMode(int showMode);
@RequiresNoPermission
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 2887d22..fa8fe3b 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1755,6 +1755,12 @@
private int mNavigationBarColor;
@Appearance
private int mSystemBarsAppearance;
+ /**
+ * Similar to {@link TaskDescription#mSystemBarsAppearance}, but is taken from the topmost
+ * fully opaque (i.e. non transparent) activity in the task.
+ */
+ @Appearance
+ private int mTopOpaqueSystemBarsAppearance;
private boolean mEnsureStatusBarContrastWhenTransparent;
private boolean mEnsureNavigationBarContrastWhenTransparent;
private int mResizeMode;
@@ -1855,7 +1861,7 @@
final Icon icon = mIconRes == Resources.ID_NULL ? null :
Icon.createWithResource(ActivityThread.currentPackageName(), mIconRes);
return new TaskDescription(mLabel, icon, mPrimaryColor, mBackgroundColor,
- mStatusBarColor, mNavigationBarColor, 0, false, false,
+ mStatusBarColor, mNavigationBarColor, 0, 0, false, false,
RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
}
@@ -1874,7 +1880,7 @@
@Deprecated
public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
- colorPrimary, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ colorPrimary, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
throw new RuntimeException("A TaskDescription's primary color should be opaque");
}
@@ -1892,7 +1898,7 @@
@Deprecated
public TaskDescription(String label, @DrawableRes int iconRes) {
this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
- 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ 0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1904,7 +1910,7 @@
*/
@Deprecated
public TaskDescription(String label) {
- this(label, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ this(label, null, 0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1914,7 +1920,7 @@
*/
@Deprecated
public TaskDescription() {
- this(null, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ this(null, null, 0, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1930,7 +1936,7 @@
@Deprecated
public TaskDescription(String label, Bitmap icon, int colorPrimary) {
this(label, icon != null ? Icon.createWithBitmap(icon) : null, colorPrimary, 0, 0, 0,
- 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
throw new RuntimeException("A TaskDescription's primary color should be opaque");
}
@@ -1946,7 +1952,7 @@
*/
@Deprecated
public TaskDescription(String label, Bitmap icon) {
- this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, false,
+ this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, 0, false,
false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
@@ -1955,6 +1961,7 @@
int colorPrimary, int colorBackground,
int statusBarColor, int navigationBarColor,
@Appearance int systemBarsAppearance,
+ @Appearance int topOpaqueSystemBarsAppearance,
boolean ensureStatusBarContrastWhenTransparent,
boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
int minHeight, int colorBackgroundFloating) {
@@ -1965,6 +1972,7 @@
mStatusBarColor = statusBarColor;
mNavigationBarColor = navigationBarColor;
mSystemBarsAppearance = systemBarsAppearance;
+ mTopOpaqueSystemBarsAppearance = topOpaqueSystemBarsAppearance;
mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
ensureNavigationBarContrastWhenTransparent;
@@ -1994,6 +2002,7 @@
mStatusBarColor = other.mStatusBarColor;
mNavigationBarColor = other.mNavigationBarColor;
mSystemBarsAppearance = other.mSystemBarsAppearance;
+ mTopOpaqueSystemBarsAppearance = other.mTopOpaqueSystemBarsAppearance;
mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
other.mEnsureNavigationBarContrastWhenTransparent;
@@ -2026,6 +2035,9 @@
if (other.mSystemBarsAppearance != 0) {
mSystemBarsAppearance = other.mSystemBarsAppearance;
}
+ if (other.mTopOpaqueSystemBarsAppearance != 0) {
+ mTopOpaqueSystemBarsAppearance = other.mTopOpaqueSystemBarsAppearance;
+ }
mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
@@ -2305,6 +2317,14 @@
/**
* @hide
*/
+ @Appearance
+ public int getTopOpaqueSystemBarsAppearance() {
+ return mTopOpaqueSystemBarsAppearance;
+ }
+
+ /**
+ * @hide
+ */
public void setEnsureStatusBarContrastWhenTransparent(
boolean ensureStatusBarContrastWhenTransparent) {
mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
@@ -2320,6 +2340,13 @@
/**
* @hide
*/
+ public void setTopOpaqueSystemBarsAppearance(int topOpaqueSystemBarsAppearance) {
+ mTopOpaqueSystemBarsAppearance = topOpaqueSystemBarsAppearance;
+ }
+
+ /**
+ * @hide
+ */
public boolean getEnsureNavigationBarContrastWhenTransparent() {
return mEnsureNavigationBarContrastWhenTransparent;
}
@@ -2442,6 +2469,7 @@
dest.writeInt(mStatusBarColor);
dest.writeInt(mNavigationBarColor);
dest.writeInt(mSystemBarsAppearance);
+ dest.writeInt(mTopOpaqueSystemBarsAppearance);
dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
dest.writeInt(mResizeMode);
@@ -2466,6 +2494,7 @@
mStatusBarColor = source.readInt();
mNavigationBarColor = source.readInt();
mSystemBarsAppearance = source.readInt();
+ mTopOpaqueSystemBarsAppearance = source.readInt();
mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
mResizeMode = source.readInt();
@@ -2498,7 +2527,8 @@
+ " resizeMode: " + ActivityInfo.resizeModeToString(mResizeMode)
+ " minWidth: " + mMinWidth + " minHeight: " + mMinHeight
+ " colorBackgrounFloating: " + mColorBackgroundFloating
- + " systemBarsAppearance: " + mSystemBarsAppearance;
+ + " systemBarsAppearance: " + mSystemBarsAppearance
+ + " topOpaqueSystemBarsAppearance: " + mTopOpaqueSystemBarsAppearance;
}
@Override
@@ -2519,6 +2549,7 @@
result = result * 31 + mStatusBarColor;
result = result * 31 + mNavigationBarColor;
result = result * 31 + mSystemBarsAppearance;
+ result = result * 31 + mTopOpaqueSystemBarsAppearance;
result = result * 31 + (mEnsureStatusBarContrastWhenTransparent ? 1 : 0);
result = result * 31 + (mEnsureNavigationBarContrastWhenTransparent ? 1 : 0);
result = result * 31 + mResizeMode;
@@ -2542,6 +2573,7 @@
&& mStatusBarColor == other.mStatusBarColor
&& mNavigationBarColor == other.mNavigationBarColor
&& mSystemBarsAppearance == other.mSystemBarsAppearance
+ && mTopOpaqueSystemBarsAppearance == other.mTopOpaqueSystemBarsAppearance
&& mEnsureStatusBarContrastWhenTransparent
== other.mEnsureStatusBarContrastWhenTransparent
&& mEnsureNavigationBarContrastWhenTransparent
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 7dbf270..76c1ed6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -355,7 +355,7 @@
private static final String DEFAULT_FULL_BACKUP_AGENT = "android.app.backup.FullBackupAgent";
- private static final long BINDER_CALLBACK_THROTTLE_MS = 10_100L;
+ private static final long BINDER_CALLBACK_THROTTLE = 10_100L;
private long mBinderCallbackLast = -1;
/**
@@ -7551,13 +7551,12 @@
@Override
public void onTransactionError(int pid, int code, int flags, int err) {
final long now = SystemClock.uptimeMillis();
- if (now < mBinderCallbackLast + BINDER_CALLBACK_THROTTLE_MS) {
+ if (now < mBinderCallbackLast + BINDER_CALLBACK_THROTTLE) {
Slog.d(TAG, "Too many transaction errors, throttling freezer binder callback.");
return;
}
mBinderCallbackLast = now;
try {
- Log.wtfStack(TAG, "Binder Transaction Error");
mgr.frozenBinderTransactionDetected(pid, code, flags, err);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index c0f7232..5956e2b 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -2617,6 +2617,9 @@
try {
Objects.requireNonNull(packageName);
return mPM.isAppArchivable(packageName, new UserHandle(getUserId()));
+ } catch (ParcelableException e) {
+ e.maybeRethrow(NameNotFoundException.class);
+ throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 4c839f1..fc3bb02 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -114,7 +114,7 @@
import com.android.internal.graphics.ColorUtils;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ContrastColorUtil;
-import com.android.internal.util.NewlineNormalizer;
+import com.android.internal.util.NotificationBigTextNormalizer;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -1632,6 +1632,10 @@
private Icon mSmallIcon;
@UnsupportedAppUsage
private Icon mLargeIcon;
+ private Icon mAppIcon;
+
+ /** Cache for whether the notification was posted by a headless system app. */
+ private Boolean mBelongsToHeadlessSystemApp = null;
@UnsupportedAppUsage
private String mChannelId;
@@ -3079,25 +3083,17 @@
return name.toString();
}
}
- // If not, try getting the app info from extras.
+ // If not, try getting the name from the app info.
if (context == null) {
return null;
}
- final PackageManager pm = context.getPackageManager();
if (TextUtils.isEmpty(name)) {
- if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
- final ApplicationInfo info = extras.getParcelable(
- EXTRA_BUILDER_APPLICATION_INFO,
- ApplicationInfo.class);
- if (info != null) {
- name = pm.getApplicationLabel(info);
- }
+ ApplicationInfo info = getApplicationInfo(context);
+ if (info != null) {
+ final PackageManager pm = context.getPackageManager();
+ name = pm.getApplicationLabel(getApplicationInfo(context));
}
}
- // If that's still empty, use the one from the context directly.
- if (TextUtils.isEmpty(name)) {
- name = pm.getApplicationLabel(context.getApplicationInfo());
- }
// If there's still nothing, ¯\_(ツ)_/¯
if (TextUtils.isEmpty(name)) {
return null;
@@ -3109,9 +3105,89 @@
}
/**
+ * Whether this notification was posted by a headless system app.
+ *
+ * If we don't have enough information to figure this out, this will return false. Therefore,
+ * false negatives are possible, but false positives should not be.
+ *
* @hide
*/
- public int loadHeaderAppIconRes(Context context) {
+ public boolean belongsToHeadlessSystemApp(Context context) {
+ Trace.beginSection("Notification#belongsToHeadlessSystemApp");
+
+ try {
+ if (mBelongsToHeadlessSystemApp != null) {
+ return mBelongsToHeadlessSystemApp;
+ }
+
+ if (context == null) {
+ // Without a valid context, we don't know exactly. Let's assume it doesn't belong to
+ // a system app, but not cache the value.
+ return false;
+ }
+
+ ApplicationInfo info = getApplicationInfo(context);
+ if (info != null) {
+ if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
+ // It's not a system app at all.
+ mBelongsToHeadlessSystemApp = false;
+ } else {
+ // If there's no launch intent, it's probably a headless app.
+ final PackageManager pm = context.getPackageManager();
+ mBelongsToHeadlessSystemApp = pm.getLaunchIntentForPackage(info.packageName)
+ == null;
+ }
+ } else {
+ // If for some reason we don't have the app info, we don't know; best assume it's
+ // not a system app.
+ return false;
+ }
+ return mBelongsToHeadlessSystemApp;
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ /**
+ * Get the resource ID of the app icon from application info.
+ * @hide
+ */
+ public int getHeaderAppIconRes(Context context) {
+ ApplicationInfo info = getApplicationInfo(context);
+ if (info != null) {
+ return info.icon;
+ }
+ return 0;
+ }
+
+ /**
+ * Load the app icon drawable from the package manager. This could result in a binder call.
+ * @hide
+ */
+ public Drawable loadHeaderAppIcon(Context context) {
+ Trace.beginSection("Notification#loadHeaderAppIcon");
+
+ try {
+ if (context == null) {
+ Log.e(TAG, "Cannot load the app icon drawable with a null context");
+ return null;
+ }
+ final PackageManager pm = context.getPackageManager();
+ ApplicationInfo info = getApplicationInfo(context);
+ if (info == null) {
+ Log.e(TAG, "Cannot load the app icon drawable: no application info");
+ return null;
+ }
+ return pm.getApplicationIcon(info);
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ /**
+ * Fetch the application info from the notification, or the context if that isn't available.
+ */
+ private ApplicationInfo getApplicationInfo(Context context) {
ApplicationInfo info = null;
if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
info = extras.getParcelable(
@@ -3119,12 +3195,12 @@
ApplicationInfo.class);
}
if (info == null) {
+ if (context == null) {
+ return null;
+ }
info = context.getApplicationInfo();
}
- if (info != null) {
- return info.icon;
- }
- return 0;
+ return info;
}
/**
@@ -3186,12 +3262,12 @@
return cs.toString();
}
- private static CharSequence cleanUpNewLines(@Nullable CharSequence charSequence) {
+ private static CharSequence normalizeBigText(@Nullable CharSequence charSequence) {
if (charSequence == null) {
return charSequence;
}
- return NewlineNormalizer.normalizeNewlines(charSequence.toString());
+ return NotificationBigTextNormalizer.normalizeBigText(charSequence.toString());
}
private static CharSequence removeTextSizeSpans(CharSequence charSequence) {
@@ -4124,6 +4200,55 @@
}
/**
+ * The colored app icon that can replace the small icon in the notification starting in V.
+ *
+ * Before using this value, you should first check whether it's actually being used by the
+ * notification by calling {@link Notification#shouldUseAppIcon()}.
+ *
+ * @hide
+ */
+ public Icon getAppIcon() {
+ if (mAppIcon != null) {
+ return mAppIcon;
+ }
+ // If the app icon hasn't been loaded yet, check if we can load it without a context.
+ if (extras.containsKey(EXTRA_BUILDER_APPLICATION_INFO)) {
+ final ApplicationInfo info = extras.getParcelable(
+ EXTRA_BUILDER_APPLICATION_INFO,
+ ApplicationInfo.class);
+ if (info != null) {
+ int appIconRes = info.icon;
+ if (appIconRes == 0) {
+ Log.w(TAG, "Failed to get the app icon: no icon in application info");
+ return null;
+ }
+ mAppIcon = Icon.createWithResource(info.packageName, appIconRes);
+ return mAppIcon;
+ } else {
+ Log.e(TAG, "Failed to get the app icon: "
+ + "there's an EXTRA_BUILDER_APPLICATION_INFO in extras but it's null");
+ }
+ } else {
+ Log.w(TAG, "Failed to get the app icon: no application info in extras");
+ }
+ return null;
+ }
+
+ /**
+ * Whether the notification is using the app icon instead of the small icon.
+ * @hide
+ */
+ public boolean shouldUseAppIcon() {
+ if (Flags.notificationsUseAppIconInRow()) {
+ if (belongsToHeadlessSystemApp(/* context = */ null)) {
+ return false;
+ }
+ return getAppIcon() != null;
+ }
+ return false;
+ }
+
+ /**
* The large icon shown in this notification's content view.
* @see Builder#getLargeIcon()
* @see Builder#setLargeIcon(Icon)
@@ -6116,16 +6241,30 @@
if (Flags.notificationsUseAppIcon()) {
// Override small icon with app icon
mN.mSmallIcon = Icon.createWithResource(mContext,
- mN.loadHeaderAppIconRes(mContext));
+ mN.getHeaderAppIconRes(mContext));
} else if (mN.mSmallIcon == null && mN.icon != 0) {
mN.mSmallIcon = Icon.createWithResource(mContext, mN.icon);
}
- contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
+ boolean usingAppIcon = false;
+ if (Flags.notificationsUseAppIconInRow() && !mN.belongsToHeadlessSystemApp(mContext)) {
+ // Use the app icon in the view
+ int appIconRes = mN.getHeaderAppIconRes(mContext);
+ if (appIconRes != 0) {
+ mN.mAppIcon = Icon.createWithResource(mContext, appIconRes);
+ contentView.setImageViewIcon(R.id.icon, mN.mAppIcon);
+ usingAppIcon = true;
+ } else {
+ Log.w(TAG, "bindSmallIcon: could not get the app icon");
+ }
+ }
+ if (!usingAppIcon) {
+ contentView.setImageViewIcon(R.id.icon, mN.mSmallIcon);
+ }
contentView.setInt(R.id.icon, "setImageLevel", mN.iconLevel);
// Don't change color if we're using the app icon.
- if (!Flags.notificationsUseAppIcon()) {
+ if (!Flags.notificationsUseAppIcon() && !usingAppIcon) {
processSmallIconColor(mN.mSmallIcon, contentView, p);
}
}
@@ -8427,7 +8566,7 @@
// Replace the text with the big text, but only if the big text is not empty.
CharSequence bigTextText = mBuilder.processLegacyText(mBigText);
if (Flags.cleanUpSpansAndNewLines()) {
- bigTextText = cleanUpNewLines(stripStyling(bigTextText));
+ bigTextText = normalizeBigText(stripStyling(bigTextText));
}
if (!TextUtils.isEmpty(bigTextText)) {
p.text(bigTextText);
diff --git a/core/java/android/app/UiAutomation.java b/core/java/android/app/UiAutomation.java
index 348d4d8f..273a79e 100644
--- a/core/java/android/app/UiAutomation.java
+++ b/core/java/android/app/UiAutomation.java
@@ -1969,6 +1969,11 @@
}
@Override
+ public void onMagnificationSystemUIConnectionChanged(boolean connected) {
+ /* do nothing */
+ }
+
+ @Override
public void onMagnificationChanged(int displayId, @NonNull Region region,
MagnificationConfig config) {
/* do nothing */
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index 55c3bb60..2d78317 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -52,14 +52,32 @@
bug: "281044385"
}
+# vvv Prototypes for using app icons in notifications vvv
+
flag {
name: "notifications_use_app_icon"
namespace: "systemui"
- description: "Experiment to replace the small icon in a notification with the app icon."
+ description: "Experiment to replace the small icon in a notification with the app icon. This includes the status bar, AOD, shelf and notification row itself."
bug: "335211019"
}
flag {
+ name: "notifications_use_app_icon_in_row"
+ namespace: "systemui"
+ description: "Experiment to replace the small icon in a notification row with the app icon."
+ bug: "335211019"
+}
+
+flag {
+ name: "notifications_use_monochrome_app_icon"
+ namespace: "systemui"
+ description: "Experiment to replace the notification icon in the status bar and shelf with the monochrome app icon, if available."
+ bug: "335211019"
+}
+
+# ^^^ Prototypes for using app icons in notifications ^^^
+
+flag {
name: "notification_expansion_optional"
namespace: "systemui"
description: "Experiment to restore the pre-S behavior where standard notifications are not expandable unless they have actions."
@@ -174,3 +192,10 @@
description: "Removes all custom views"
bug: "342602960"
}
+
+flag {
+ name: "redact_sensitive_content_notifications_on_lockscreen"
+ namespace: "systemui"
+ description: "redacts notifications on the lockscreen if they have the 'sensitiveContent' flag"
+ bug: "343631648"
+}
diff --git a/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java b/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java
index 1c8e497..eb31db1 100644
--- a/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java
+++ b/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java
@@ -27,6 +27,8 @@
import android.view.InsetsSourceControl;
import android.view.InsetsState;
+import com.android.internal.annotations.VisibleForTesting;
+
import java.util.Objects;
/**
@@ -38,7 +40,9 @@
private static final String TAG = "WindowStateInsetsControlChangeItem";
private InsetsState mInsetsState;
- private InsetsSourceControl.Array mActiveControls;
+
+ @VisibleForTesting
+ public InsetsSourceControl.Array mActiveControls;
@Override
public void execute(@NonNull ClientTransactionHandler client, @NonNull IWindow window,
@@ -51,6 +55,8 @@
// An exception could happen if the process is restarted. It is safe to ignore since
// the window should no longer exist.
Log.w(TAG, "The original window no longer exists in the new process", e);
+ // Prevent leak
+ mActiveControls.release();
}
Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
}
@@ -69,7 +75,12 @@
}
instance.setWindow(window);
instance.mInsetsState = new InsetsState(insetsState, true /* copySources */);
- instance.mActiveControls = new InsetsSourceControl.Array(activeControls);
+ instance.mActiveControls = new InsetsSourceControl.Array(
+ activeControls, true /* copyControls */);
+ // This source control is an extra copy if the client is not local. By setting
+ // PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of
+ // SurfaceControl.writeToParcel.
+ instance.mActiveControls.setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE);
return instance;
}
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 374be6f..18cfca6 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -40,3 +40,13 @@
description: "Throttle the widget view updates to mitigate transaction exceptions"
bug: "326145514"
}
+
+flag {
+ name: "support_resume_restore_after_reboot"
+ namespace: "app_widgets"
+ description: "Enable support for resume restore widget after reboot by persisting intermediate states to disk"
+ bug: "336976070"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/companion/virtual/flags/flags.aconfig b/core/java/android/companion/virtual/flags/flags.aconfig
index ed5d662..1e781532 100644
--- a/core/java/android/companion/virtual/flags/flags.aconfig
+++ b/core/java/android/companion/virtual/flags/flags.aconfig
@@ -64,3 +64,13 @@
purpose: PURPOSE_BUGFIX
}
}
+
+flag {
+ namespace: "virtual_devices"
+ name: "intent_interception_action_matching_fix"
+ description: "Do not match intents without actions if the filter has actions"
+ bug: "343805037"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index 270fc32..bbd0e9f 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -2431,6 +2431,7 @@
statusReceiver, new UserHandle(mUserId));
} catch (ParcelableException e) {
e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2467,6 +2468,7 @@
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -2499,6 +2501,7 @@
userActionIntent, new UserHandle(mUserId));
} catch (ParcelableException e) {
e.maybeRethrow(PackageManager.NameNotFoundException.class);
+ throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index e2a131c..5668c54 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -140,6 +140,16 @@
}
}
+flag {
+ name: "fix_avatar_concurrent_file_write"
+ namespace: "multiuser"
+ description: "Fix potential unexpected behavior due to concurrent file writing"
+ bug: "339351031"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
+
# This flag guards the private space feature and all its implementations excluding the APIs. APIs are guarded by android.os.Flags.allow_private_profile.
flag {
name: "enable_private_space_features"
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index e3780ed..f54be00 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -733,7 +733,7 @@
* commits, or is rolled back, either explicitly or by a call to
* {@link #yieldIfContendedSafely}.
*/
- // TODO(340874899) Provide an Executor overload
+ @SuppressLint("ExecutorRegistration")
public void beginTransactionWithListener(
@Nullable SQLiteTransactionListener transactionListener) {
beginTransaction(transactionListener, true);
@@ -763,7 +763,7 @@
* transaction begins, commits, or is rolled back, either
* explicitly or by a call to {@link #yieldIfContendedSafely}.
*/
- // TODO(340874899) Provide an Executor overload
+ @SuppressLint("ExecutorRegistration")
public void beginTransactionWithListenerNonExclusive(
@Nullable SQLiteTransactionListener transactionListener) {
beginTransaction(transactionListener, false);
@@ -789,7 +789,6 @@
* }
* </pre>
*/
- // TODO(340874899) Provide an Executor overload
@SuppressLint("ExecutorRegistration")
@FlaggedApi(Flags.FLAG_SQLITE_APIS_35)
public void beginTransactionWithListenerReadOnly(
diff --git a/core/java/android/hardware/biometrics/flags.aconfig b/core/java/android/hardware/biometrics/flags.aconfig
index 4284ad0..047d1fa 100644
--- a/core/java/android/hardware/biometrics/flags.aconfig
+++ b/core/java/android/hardware/biometrics/flags.aconfig
@@ -32,3 +32,10 @@
description: "Feature flag for adding a custom content view API to BiometricPrompt.Builder."
bug: "302735104"
}
+
+flag {
+ name: "mandatory_biometrics"
+ namespace: "biometrics_framework"
+ description: "This flag controls whether LSKF fallback is removed from biometric prompt when the phone is outside trusted locations"
+ bug: "322081563"
+}
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index 342479b..3cc87ea 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -1492,10 +1492,12 @@
/**
* <p>Default flash brightness level for manual flash control in SINGLE mode.</p>
* <p>If flash unit is available this will be greater than or equal to 1 and less
- * or equal to <code>android.flash.info.singleStrengthMaxLevel</code>.
+ * or equal to {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}.
* Note for devices that do not support the manual flash strength control
* feature, this level will always be equal to 1.</p>
* <p>This key is available on all devices.</p>
+ *
+ * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL
*/
@PublicKey
@NonNull
@@ -1511,13 +1513,15 @@
* otherwise the value will be equal to 1.</p>
* <p>Note that this level is just a number of supported levels(the granularity of control).
* There is no actual physical power units tied to this level.
- * There is no relation between android.flash.info.torchStrengthMaxLevel and
- * android.flash.info.singleStrengthMaxLevel i.e. the ratio of
- * android.flash.info.torchStrengthMaxLevel:android.flash.info.singleStrengthMaxLevel
+ * There is no relation between {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} and
+ * {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} i.e. the ratio of
+ * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}:{@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}
* is not guaranteed to be the ratio of actual brightness.</p>
* <p>This key is available on all devices.</p>
*
* @see CaptureRequest#FLASH_MODE
+ * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL
+ * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL
*/
@PublicKey
@NonNull
@@ -1528,10 +1532,12 @@
/**
* <p>Default flash brightness level for manual flash control in TORCH mode</p>
* <p>If flash unit is available this will be greater than or equal to 1 and less
- * or equal to android.flash.info.torchStrengthMaxLevel.
+ * or equal to {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}.
* Note for the devices that do not support the manual flash strength control feature,
* this level will always be equal to 1.</p>
* <p>This key is available on all devices.</p>
+ *
+ * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL
*/
@PublicKey
@NonNull
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index c82e7e8..938636f 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -2684,35 +2684,39 @@
* <p>Flash strength level to be used when manual flash control is active.</p>
* <p>Flash strength level to use in capture mode i.e. when the applications control
* flash with either SINGLE or TORCH mode.</p>
- * <p>Use android.flash.info.singleStrengthMaxLevel and
- * android.flash.info.torchStrengthMaxLevel to check whether the device supports
+ * <p>Use {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} and
+ * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} to check whether the device supports
* flash strength control or not.
* If the values of android.flash.info.singleStrengthMaxLevel and
- * android.flash.info.torchStrengthMaxLevel are greater than 1,
+ * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} are greater than 1,
* then the device supports manual flash strength control.</p>
* <p>If the {@link CaptureRequest#FLASH_MODE android.flash.mode} <code>==</code> TORCH the value must be >= 1
- * and <= android.flash.info.torchStrengthMaxLevel.
+ * and <= {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}.
* If the application doesn't set the key and
- * android.flash.info.torchStrengthMaxLevel > 1,
+ * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} > 1,
* then the flash will be fired at the default level set by HAL in
- * android.flash.info.torchStrengthDefaultLevel.
+ * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_DEFAULT_LEVEL android.flash.torchStrengthDefaultLevel}.
* If the {@link CaptureRequest#FLASH_MODE android.flash.mode} <code>==</code> SINGLE, then the value must be >= 1
- * and <= android.flash.info.singleStrengthMaxLevel.
+ * and <= {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}.
* If the application does not set this key and
- * android.flash.info.singleStrengthMaxLevel > 1,
+ * {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} > 1,
* then the flash will be fired at the default level set by HAL
- * in android.flash.info.singleStrengthDefaultLevel.
+ * in {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL android.flash.singleStrengthDefaultLevel}.
* If {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is set to any of ON_AUTO_FLASH, ON_ALWAYS_FLASH,
* ON_AUTO_FLASH_REDEYE, ON_EXTERNAL_FLASH values, then the strengthLevel will be ignored.</p>
* <p><b>Range of valid values:</b><br>
- * <code>[1-android.flash.info.torchStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
+ * <code>[1-{@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
* set to TORCH;
- * <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
+ * <code>[1-{@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
* set to SINGLE</p>
* <p>This key is available on all devices.</p>
*
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#FLASH_MODE
+ * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL
+ * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL
+ * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_DEFAULT_LEVEL
+ * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL
*/
@PublicKey
@NonNull
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index 1460515..4406a41 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -2977,35 +2977,39 @@
* <p>Flash strength level to be used when manual flash control is active.</p>
* <p>Flash strength level to use in capture mode i.e. when the applications control
* flash with either SINGLE or TORCH mode.</p>
- * <p>Use android.flash.info.singleStrengthMaxLevel and
- * android.flash.info.torchStrengthMaxLevel to check whether the device supports
+ * <p>Use {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} and
+ * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} to check whether the device supports
* flash strength control or not.
* If the values of android.flash.info.singleStrengthMaxLevel and
- * android.flash.info.torchStrengthMaxLevel are greater than 1,
+ * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} are greater than 1,
* then the device supports manual flash strength control.</p>
* <p>If the {@link CaptureRequest#FLASH_MODE android.flash.mode} <code>==</code> TORCH the value must be >= 1
- * and <= android.flash.info.torchStrengthMaxLevel.
+ * and <= {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}.
* If the application doesn't set the key and
- * android.flash.info.torchStrengthMaxLevel > 1,
+ * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel} > 1,
* then the flash will be fired at the default level set by HAL in
- * android.flash.info.torchStrengthDefaultLevel.
+ * {@link CameraCharacteristics#FLASH_TORCH_STRENGTH_DEFAULT_LEVEL android.flash.torchStrengthDefaultLevel}.
* If the {@link CaptureRequest#FLASH_MODE android.flash.mode} <code>==</code> SINGLE, then the value must be >= 1
- * and <= android.flash.info.singleStrengthMaxLevel.
+ * and <= {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}.
* If the application does not set this key and
- * android.flash.info.singleStrengthMaxLevel > 1,
+ * {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel} > 1,
* then the flash will be fired at the default level set by HAL
- * in android.flash.info.singleStrengthDefaultLevel.
+ * in {@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL android.flash.singleStrengthDefaultLevel}.
* If {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is set to any of ON_AUTO_FLASH, ON_ALWAYS_FLASH,
* ON_AUTO_FLASH_REDEYE, ON_EXTERNAL_FLASH values, then the strengthLevel will be ignored.</p>
* <p><b>Range of valid values:</b><br>
- * <code>[1-android.flash.info.torchStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
+ * <code>[1-{@link CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL android.flash.torchStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
* set to TORCH;
- * <code>[1-android.flash.info.singleStrengthMaxLevel]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
+ * <code>[1-{@link CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL android.flash.singleStrengthMaxLevel}]</code> when the {@link CaptureRequest#FLASH_MODE android.flash.mode} is
* set to SINGLE</p>
* <p>This key is available on all devices.</p>
*
* @see CaptureRequest#CONTROL_AE_MODE
* @see CaptureRequest#FLASH_MODE
+ * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_DEFAULT_LEVEL
+ * @see CameraCharacteristics#FLASH_SINGLE_STRENGTH_MAX_LEVEL
+ * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_DEFAULT_LEVEL
+ * @see CameraCharacteristics#FLASH_TORCH_STRENGTH_MAX_LEVEL
*/
@PublicKey
@NonNull
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 9eabc8d..53771e3 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -963,11 +963,10 @@
* originate from the system, just that we were unable to verify it. This can
* happen for a number of reasons during normal operation.
*
- * @param event The {@link android.view.InputEvent} to check
+ * @param event The {@link android.view.InputEvent} to check.
*
* @return {@link android.view.VerifiedInputEvent}, which is a subset of the provided
- * {@link android.view.InputEvent}
- * {@code null} if the event could not be verified.
+ * {@link android.view.InputEvent}, or {@code null} if the event could not be verified.
*/
@Nullable
public VerifiedInputEvent verifyInputEvent(@NonNull InputEvent event) {
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index f357ebf..7933212 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -19930,6 +19930,12 @@
public static final String NETWORK_LOCATION_OPT_IN = "network_location_opt_in";
/**
+ * Whether haptics are enabled for Active Unlock on wear.
+ * @hide
+ */
+ public static final String VIBRATE_FOR_ACTIVE_UNLOCK = "wear_vibrate_for_active_unlock";
+
+ /**
* The custom foreground color.
* @hide
*/
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 71066ac..3f9c819 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1276,13 +1276,22 @@
});
}
+ /**
+ * Whether or not wake requests will be redirected.
+ *
+ * @hide
+ */
+ public boolean getRedirectWake() {
+ return mOverlayConnection != null && mRedirectWake;
+ }
+
private void wakeUp(boolean fromSystem) {
if (mDebug) {
Slog.v(mTag, "wakeUp(): fromSystem=" + fromSystem + ", mWaking=" + mWaking
+ ", mFinished=" + mFinished);
}
- if (!fromSystem && mOverlayConnection != null && mRedirectWake) {
+ if (!fromSystem && getRedirectWake()) {
mOverlayConnection.addConsumer(overlay -> {
try {
overlay.onWakeRequested();
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index c674968..0dec13f 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -18,9 +18,10 @@
import static com.android.graphics.hwui.flags.Flags.highContrastTextLuminance;
import static com.android.text.flags.Flags.FLAG_FIX_LINE_HEIGHT_FOR_LOCALE;
-import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
import static com.android.text.flags.Flags.FLAG_LETTER_SPACING_JUSTIFICATION;
+import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+import android.annotation.ColorInt;
import android.annotation.FlaggedApi;
import android.annotation.FloatRange;
import android.annotation.IntDef;
@@ -398,6 +399,20 @@
mUseBoundsForWidth = useBoundsForWidth;
mShiftDrawingOffsetForStartOverhang = shiftDrawingOffsetForStartOverhang;
mMinimumFontMetrics = minimumFontMetrics;
+
+ initSpanColors();
+ }
+
+ private void initSpanColors() {
+ if (mSpannedText && Flags.highContrastTextSmallTextRect()) {
+ if (mSpanColors == null) {
+ mSpanColors = new SpanColors();
+ } else {
+ mSpanColors.recycle();
+ }
+ } else {
+ mSpanColors = null;
+ }
}
/**
@@ -417,6 +432,7 @@
mSpacingMult = spacingmult;
mSpacingAdd = spacingadd;
mSpannedText = text instanceof Spanned;
+ initSpanColors();
}
/**
@@ -643,20 +659,20 @@
return null;
}
- return isHighContrastTextDark() ? BlendMode.MULTIPLY : BlendMode.DIFFERENCE;
+ return isHighContrastTextDark(mPaint.getColor()) ? BlendMode.MULTIPLY
+ : BlendMode.DIFFERENCE;
}
- private boolean isHighContrastTextDark() {
+ private boolean isHighContrastTextDark(@ColorInt int color) {
// High-contrast text mode
// Determine if the text is black-on-white or white-on-black, so we know what blendmode will
// give the highest contrast and most realistic text color.
// This equation should match the one in libs/hwui/hwui/DrawTextFunctor.h
if (highContrastTextLuminance()) {
var lab = new double[3];
- ColorUtils.colorToLAB(mPaint.getColor(), lab);
- return lab[0] < 0.5;
+ ColorUtils.colorToLAB(color, lab);
+ return lab[0] < 50.0;
} else {
- var color = mPaint.getColor();
int channelSum = Color.red(color) + Color.green(color) + Color.blue(color);
return channelSum < (128 * 3);
}
@@ -1010,15 +1026,22 @@
var padding = Math.max(HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX,
mPaint.getTextSize() * HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR);
+ var originalTextColor = mPaint.getColor();
var bgPaint = mWorkPlainPaint;
bgPaint.reset();
- bgPaint.setColor(isHighContrastTextDark() ? Color.WHITE : Color.BLACK);
+ bgPaint.setColor(isHighContrastTextDark(originalTextColor) ? Color.WHITE : Color.BLACK);
bgPaint.setStyle(Paint.Style.FILL);
int start = getLineStart(firstLine);
int end = getLineEnd(lastLine);
// Draw a separate background rectangle for each line of text, that only surrounds the
- // characters on that line.
+ // characters on that line. But we also have to check the text color for each character, and
+ // make sure we are drawing the correct contrasting background. This is because Spans can
+ // change colors throughout the text and we'll need to match our backgrounds.
+ if (mSpannedText && mSpanColors != null) {
+ mSpanColors.init(mWorkPaint, ((Spanned) mText), start, end);
+ }
+
forEachCharacterBounds(
start,
end,
@@ -1028,13 +1051,24 @@
int mLastLineNum = -1;
final RectF mLineBackground = new RectF();
+ @ColorInt int mLastColor = originalTextColor;
+
@Override
public void onCharacterBounds(int index, int lineNum, float left, float top,
float right, float bottom) {
- if (lineNum != mLastLineNum) {
+
+ var newBackground = determineContrastingBackgroundColor(index);
+ var hasBgColorChanged = newBackground != bgPaint.getColor();
+
+ if (lineNum != mLastLineNum || hasBgColorChanged) {
+ // Draw what we have so far, then reset the rect and update its color
drawRect();
mLineBackground.set(left, top, right, bottom);
mLastLineNum = lineNum;
+
+ if (hasBgColorChanged) {
+ bgPaint.setColor(newBackground);
+ }
} else {
mLineBackground.union(left, top, right, bottom);
}
@@ -1051,8 +1085,36 @@
canvas.drawRect(mLineBackground, bgPaint);
}
}
+
+ private int determineContrastingBackgroundColor(int index) {
+ if (!mSpannedText || mSpanColors == null) {
+ // The text is not Spanned. it's all one color.
+ return bgPaint.getColor();
+ }
+
+ // Sometimes the color will change, but not enough to warrant a background
+ // color change. e.g. from black to dark grey still gets clamped to black,
+ // so the background stays white and we don't need to draw a fresh
+ // background.
+ var textColor = mSpanColors.getColorAt(index);
+ if (textColor == SpanColors.NO_COLOR_FOUND) {
+ textColor = originalTextColor;
+ }
+ var hasColorChanged = textColor != mLastColor;
+ if (hasColorChanged) {
+ mLastColor = textColor;
+
+ return isHighContrastTextDark(textColor) ? Color.WHITE : Color.BLACK;
+ }
+
+ return bgPaint.getColor();
+ }
}
);
+
+ if (mSpanColors != null) {
+ mSpanColors.recycle();
+ }
}
/**
@@ -3580,6 +3642,7 @@
private float mSpacingAdd;
private static final Rect sTempRect = new Rect();
private boolean mSpannedText;
+ @Nullable private SpanColors mSpanColors;
private TextDirectionHeuristic mTextDir;
private SpanSet<LineBackgroundSpan> mLineBackgroundSpans;
private boolean mIncludePad;
diff --git a/core/java/android/text/SpanColors.java b/core/java/android/text/SpanColors.java
new file mode 100644
index 0000000..fcd242b
--- /dev/null
+++ b/core/java/android/text/SpanColors.java
@@ -0,0 +1,89 @@
+/*
+ * 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.text;
+
+import android.annotation.ColorInt;
+import android.annotation.Nullable;
+import android.graphics.Color;
+import android.text.style.CharacterStyle;
+
+/**
+ * Finds the foreground text color for the given Spanned text so you can iterate through each color
+ * change.
+ *
+ * @hide
+ */
+public class SpanColors {
+ public static final @ColorInt int NO_COLOR_FOUND = Color.TRANSPARENT;
+
+ private final SpanSet<CharacterStyle> mCharacterStyleSpanSet =
+ new SpanSet<>(CharacterStyle.class);
+ @Nullable private TextPaint mWorkPaint;
+
+ public SpanColors() {}
+
+ /**
+ * Init for the given text
+ *
+ * @param workPaint A temporary TextPaint object that will be used to calculate the colors. The
+ * paint properties will be mutated on calls to {@link #getColorAt(int)} so
+ * make sure to reset it before you use it for something else.
+ * @param spanned the text to examine
+ * @param start index to start at
+ * @param end index of the end
+ */
+ public void init(TextPaint workPaint, Spanned spanned, int start, int end) {
+ mWorkPaint = workPaint;
+ mCharacterStyleSpanSet.init(spanned, start, end);
+ }
+
+ /**
+ * Removes all internal references to the spans to avoid memory leaks.
+ */
+ public void recycle() {
+ mWorkPaint = null;
+ mCharacterStyleSpanSet.recycle();
+ }
+
+ /**
+ * Calculates the foreground color of the text at the given character index.
+ *
+ * <p>You must call {@link #init(TextPaint, Spanned, int, int)} before calling this
+ */
+ public @ColorInt int getColorAt(int index) {
+ var finalColor = NO_COLOR_FOUND;
+ // Reset the paint so if we get a CharacterStyle that doesn't actually specify color,
+ // (like UnderlineSpan), we still return no color found.
+ mWorkPaint.setColor(finalColor);
+ for (int k = 0; k < mCharacterStyleSpanSet.numberOfSpans; k++) {
+ if ((index >= mCharacterStyleSpanSet.spanStarts[k])
+ && (index <= mCharacterStyleSpanSet.spanEnds[k])) {
+ final CharacterStyle span = mCharacterStyleSpanSet.spans[k];
+ span.updateDrawState(mWorkPaint);
+
+ finalColor = calculateFinalColor(mWorkPaint);
+ }
+ }
+ return finalColor;
+ }
+
+ private @ColorInt int calculateFinalColor(TextPaint workPaint) {
+ // TODO: can we figure out what the getColorFilter() will do?
+ // if so, we also need to reset colorFilter before the loop in getColorAt()
+ return workPaint.getColor();
+ }
+}
diff --git a/core/java/android/tracing/perfetto/InitArguments.java b/core/java/android/tracing/perfetto/InitArguments.java
index da8c273..b4cb68c 100644
--- a/core/java/android/tracing/perfetto/InitArguments.java
+++ b/core/java/android/tracing/perfetto/InitArguments.java
@@ -26,6 +26,7 @@
*/
public class InitArguments {
public final @PerfettoBackend int backends;
+ public final int shmemSizeHintKb;
/**
* @hide
@@ -44,11 +45,21 @@
// on Linux/Android/Mac uses a named UNIX socket).
public static final int PERFETTO_BACKEND_SYSTEM = (1 << 1);
- public static InitArguments DEFAULTS = new InitArguments(PERFETTO_BACKEND_SYSTEM);
+ public static InitArguments DEFAULTS = new InitArguments(PERFETTO_BACKEND_SYSTEM, 0);
- public static InitArguments TESTING = new InitArguments(PERFETTO_BACKEND_IN_PROCESS);
+ public static InitArguments TESTING = new InitArguments(PERFETTO_BACKEND_IN_PROCESS, 0);
- public InitArguments(@PerfettoBackend int backends) {
+ /**
+ * Perfetto initialization arguments.
+ *
+ * @param backends Bitwise-or of backends that should be enabled.
+ * @param shmemSizeHintKb [Optional] Tune the size of the shared memory buffer between the
+ * current process and the service backend(s). This is a trade-off between memory footprint and
+ * the ability to sustain bursts of trace writes. If set, the value must be a multiple of 4KB.
+ * The value can be ignored if larger than kMaxShmSize (32MB) or not a multiple of 4KB.
+ */
+ public InitArguments(@PerfettoBackend int backends, int shmemSizeHintKb) {
this.backends = backends;
+ this.shmemSizeHintKb = shmemSizeHintKb;
}
}
diff --git a/core/java/android/tracing/perfetto/Producer.java b/core/java/android/tracing/perfetto/Producer.java
index a1b3eb7..13582e8 100644
--- a/core/java/android/tracing/perfetto/Producer.java
+++ b/core/java/android/tracing/perfetto/Producer.java
@@ -27,8 +27,8 @@
* @param args arguments on how to initialize the Perfetto producer.
*/
public static void init(InitArguments args) {
- nativePerfettoProducerInit(args.backends);
+ nativePerfettoProducerInit(args.backends, args.shmemSizeHintKb);
}
- private static native void nativePerfettoProducerInit(int backends);
+ private static native void nativePerfettoProducerInit(int backends, int shmemSizeHintKb);
}
diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java
index ba1915c..15b0c13 100644
--- a/core/java/android/view/Display.java
+++ b/core/java/android/view/Display.java
@@ -2263,6 +2263,7 @@
this(modeId, width, height, refreshRate, vsyncRate, false, alternativeRefreshRates,
supportedHdrTypes);
}
+
/**
* @hide
*/
diff --git a/core/java/android/view/DisplayInfo.java b/core/java/android/view/DisplayInfo.java
index da86e2d..8b9d876 100644
--- a/core/java/android/view/DisplayInfo.java
+++ b/core/java/android/view/DisplayInfo.java
@@ -213,7 +213,7 @@
/**
* The supported modes that will be exposed externally.
- * Might have different set of modes that supportedModes for VRR displays
+ * Might have different set of modes than supportedModes for VRR displays
*/
public Display.Mode[] appsSupportedModes = Display.Mode.EMPTY_ARRAY;
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index a9846fb..eec805b7 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -303,7 +303,7 @@
}
/** Not running an animation. */
- @VisibleForTesting(visibility = PACKAGE)
+ @VisibleForTesting
public static final int ANIMATION_TYPE_NONE = -1;
/** Running animation will show insets */
@@ -317,7 +317,7 @@
public static final int ANIMATION_TYPE_USER = 2;
/** Running animation will resize insets */
- @VisibleForTesting(visibility = PACKAGE)
+ @VisibleForTesting
public static final int ANIMATION_TYPE_RESIZE = 3;
@Retention(RetentionPolicy.SOURCE)
@@ -1712,7 +1712,7 @@
mImeSourceConsumer.onWindowFocusLost();
}
- @VisibleForTesting(visibility = PACKAGE)
+ @VisibleForTesting
public @AnimationType int getAnimationType(@InsetsType int type) {
for (int i = mRunningAnimations.size() - 1; i >= 0; i--) {
InsetsAnimationControlRunner control = mRunningAnimations.get(i).runner;
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 6c670f5..fdb2a6e 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -17,7 +17,6 @@
package android.view;
import static android.view.InsetsController.ANIMATION_TYPE_NONE;
-import static android.view.InsetsController.ANIMATION_TYPE_RESIZE;
import static android.view.InsetsController.AnimationType;
import static android.view.InsetsController.DEBUG;
import static android.view.InsetsSourceConsumerProto.ANIMATION_STATE;
@@ -32,7 +31,6 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
-import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
import android.util.proto.ProtoOutputStream;
@@ -181,11 +179,10 @@
mController.notifyVisibilityChanged();
}
- // If there is no animation controlling the leash, make sure the visibility and the
- // position is up-to-date.
- final int animType = mController.getAnimationType(mType);
- if (animType == ANIMATION_TYPE_NONE || animType == ANIMATION_TYPE_RESIZE) {
- applyRequestedVisibilityAndPositionToControl();
+ // If we have a new leash, make sure visibility is up-to-date, even though we
+ // didn't want to run an animation above.
+ if (mController.getAnimationType(mType) == ANIMATION_TYPE_NONE) {
+ applyRequestedVisibilityToControl();
}
// Remove the surface that owned by last control when it lost.
@@ -374,27 +371,21 @@
if (DEBUG) Log.d(TAG, "updateSource: " + newSource);
}
- private void applyRequestedVisibilityAndPositionToControl() {
- if (mSourceControl == null) {
- return;
- }
- final SurfaceControl leash = mSourceControl.getLeash();
- if (leash == null) {
+ private void applyRequestedVisibilityToControl() {
+ if (mSourceControl == null || mSourceControl.getLeash() == null) {
return;
}
final boolean requestedVisible = (mController.getRequestedVisibleTypes() & mType) != 0;
- final Point surfacePosition = mSourceControl.getSurfacePosition();
try (Transaction t = mTransactionSupplier.get()) {
if (DEBUG) Log.d(TAG, "applyRequestedVisibilityToControl: " + requestedVisible);
if (requestedVisible) {
- t.show(leash);
+ t.show(mSourceControl.getLeash());
} else {
- t.hide(leash);
+ t.hide(mSourceControl.getLeash());
}
// Ensure the alpha value is aligned with the actual requested visibility.
- t.setAlpha(leash, requestedVisible ? 1 : 0);
- t.setPosition(leash, surfacePosition.x, surfacePosition.y);
+ t.setAlpha(mSourceControl.getLeash(), requestedVisible ? 1 : 0);
t.apply();
}
onPerceptible(requestedVisible);
diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java
index 4e5cb58..487214c 100644
--- a/core/java/android/view/InsetsSourceControl.java
+++ b/core/java/android/view/InsetsSourceControl.java
@@ -269,22 +269,66 @@
public Array() {
}
- public Array(@NonNull Array other) {
- mControls = other.mControls;
+ /**
+ * @param copyControls whether or not to make a copy of the each {@link InsetsSourceControl}
+ */
+ public Array(@NonNull Array other, boolean copyControls) {
+ setTo(other, copyControls);
}
- public Array(Parcel in) {
+ public Array(@NonNull Parcel in) {
readFromParcel(in);
}
- public void set(@Nullable InsetsSourceControl[] controls) {
- mControls = controls;
+ /** Updates the current Array to the given Array. */
+ public void setTo(@NonNull Array other, boolean copyControls) {
+ set(other.mControls, copyControls);
}
+ /** Updates the current controls to the given controls. */
+ public void set(@Nullable InsetsSourceControl[] controls, boolean copyControls) {
+ if (controls == null || !copyControls) {
+ mControls = controls;
+ return;
+ }
+ // Make a copy of the array.
+ mControls = new InsetsSourceControl[controls.length];
+ for (int i = mControls.length - 1; i >= 0; i--) {
+ if (controls[i] != null) {
+ mControls[i] = new InsetsSourceControl(controls[i]);
+ }
+ }
+ }
+
+ /** Gets the controls. */
public @Nullable InsetsSourceControl[] get() {
return mControls;
}
+ /** Cleanup {@link SurfaceControl} stored in controls to prevent leak. */
+ public void release() {
+ if (mControls == null) {
+ return;
+ }
+ for (InsetsSourceControl control : mControls) {
+ if (control != null) {
+ control.release(SurfaceControl::release);
+ }
+ }
+ }
+
+ /** Sets the given flags to all controls. */
+ public void setParcelableFlags(int parcelableFlags) {
+ if (mControls == null) {
+ return;
+ }
+ for (InsetsSourceControl control : mControls) {
+ if (control != null) {
+ control.setParcelableFlags(parcelableFlags);
+ }
+ }
+ }
+
@Override
public int describeContents() {
return 0;
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 1cb2765..075ee1b 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -23705,12 +23705,6 @@
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
- // // For VRR to vote the preferred frame rate
- if (sToolkitSetFrameRateReadOnlyFlagValue
- && sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
- votePreferredFrameRate();
- }
-
mPrivateFlags4 |= PFLAG4_HAS_DRAWN;
// Fast path for layouts with no backgrounds
@@ -23727,6 +23721,12 @@
draw(canvas);
}
}
+
+ // For VRR to vote the preferred frame rate
+ if (sToolkitSetFrameRateReadOnlyFlagValue
+ && sToolkitFrameRateViewEnablingReadOnlyFlagValue) {
+ votePreferredFrameRate();
+ }
} finally {
renderNode.endRecording();
setDisplayListProperties(renderNode);
@@ -34061,10 +34061,14 @@
}
private float convertVelocityToFrameRate(float velocityPps) {
+ // From UXR study, premium experience is:
+ // 1500+ dp/s: 120fps
+ // 0 - 1500 dp/s: 80fps
+ // OEMs are likely to modify this to balance battery and user experience for their
+ // specific device.
float density = mAttachInfo.mDensity;
float velocityDps = velocityPps / density;
- // Choose a frame rate in increments of 10fps
- return Math.min(MAX_FRAME_RATE, 60f + (10f * (float) Math.floor(velocityDps / 300f)));
+ return (velocityDps >= 1500f) ? MAX_FRAME_RATE : 80f;
}
/**
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 139285a..fd407d6 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -2276,6 +2276,29 @@
requestLayout();
}
+ /** Handles messages {@link #MSG_INSETS_CONTROL_CHANGED}. */
+ private void handleInsetsControlChanged(@NonNull InsetsState insetsState,
+ @NonNull InsetsSourceControl.Array activeControls) {
+ final InsetsSourceControl[] controls = activeControls.get();
+
+ if (mTranslator != null) {
+ mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
+ mTranslator.translateSourceControlsInScreenToAppWindow(controls);
+ }
+
+ // Deliver state change before control change, such that:
+ // a) When gaining control, controller can compare with server state to evaluate
+ // whether it needs to run animation.
+ // b) When loosing control, controller can restore server state by taking last
+ // dispatched state as truth.
+ mInsetsController.onStateChanged(insetsState);
+ if (mAdded) {
+ mInsetsController.onControlsChanged(controls);
+ } else {
+ activeControls.release();
+ }
+ }
+
private final DisplayListener mDisplayListener = new DisplayListener() {
@Override
public void onDisplayChanged(int displayId) {
@@ -2767,11 +2790,27 @@
public void bringChildToFront(View child) {
}
+ // keep in sync with getHostVisibilityReason
int getHostVisibility() {
return mView != null && (mAppVisible || mForceDecorViewVisibility)
? mView.getVisibility() : View.GONE;
}
+ String getHostVisibilityReason() {
+ if (mView == null) {
+ return "mView is null";
+ }
+ if (!mAppVisible && !mForceDecorViewVisibility) {
+ return "!mAppVisible && !mForceDecorViewVisibility";
+ }
+ switch (mView.getVisibility()) {
+ case View.VISIBLE: return "View.VISIBLE";
+ case View.GONE: return "View.GONE";
+ case View.INVISIBLE: return "View.INVISIBLE";
+ default: return "";
+ }
+ }
+
/**
* Add LayoutTransition to the list of transitions to be started in the next traversal.
* This list will be cleared after the transitions on the list are start()'ed. These
@@ -3323,6 +3362,7 @@
int desiredWindowHeight;
final int viewVisibility = getHostVisibility();
+ final String viewVisibilityReason = getHostVisibilityReason();
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded
// Also check for possible double visibility update, which will make current
@@ -4197,7 +4237,7 @@
if (!isViewVisible) {
if (mLastTraversalWasVisible) {
- logAndTrace("Not drawing due to not visible");
+ logAndTrace("Not drawing due to not visible. Reason=" + viewVisibilityReason);
}
mLastPerformTraversalsSkipDrawReason = "view_not_visible";
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
@@ -6591,24 +6631,11 @@
break;
}
case MSG_INSETS_CONTROL_CHANGED: {
- SomeArgs args = (SomeArgs) msg.obj;
-
- // Deliver state change before control change, such that:
- // a) When gaining control, controller can compare with server state to evaluate
- // whether it needs to run animation.
- // b) When loosing control, controller can restore server state by taking last
- // dispatched state as truth.
- mInsetsController.onStateChanged((InsetsState) args.arg1);
- InsetsSourceControl[] controls = (InsetsSourceControl[]) args.arg2;
- if (mAdded) {
- mInsetsController.onControlsChanged(controls);
- } else if (controls != null) {
- for (InsetsSourceControl control : controls) {
- if (control != null) {
- control.release(SurfaceControl::release);
- }
- }
- }
+ final SomeArgs args = (SomeArgs) msg.obj;
+ final InsetsState insetsState = (InsetsState) args.arg1;
+ final InsetsSourceControl.Array activeControls =
+ (InsetsSourceControl.Array) args.arg2;
+ handleInsetsControlChanged(insetsState, activeControls);
args.recycle();
break;
}
@@ -9828,25 +9855,9 @@
mHandler.sendMessage(msg);
}
- private void dispatchInsetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] activeControls) {
- if (Binder.getCallingPid() == android.os.Process.myPid()) {
- insetsState = new InsetsState(insetsState, true /* copySource */);
- if (activeControls != null) {
- for (int i = activeControls.length - 1; i >= 0; i--) {
- activeControls[i] = new InsetsSourceControl(activeControls[i]);
- }
- }
- }
- if (mTranslator != null) {
- mTranslator.translateInsetsStateInScreenToAppWindow(insetsState);
- mTranslator.translateSourceControlsInScreenToAppWindow(activeControls);
- }
- if (insetsState != null && insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) {
- ImeTracing.getInstance().triggerClientDump("ViewRootImpl#dispatchInsetsControlChanged",
- getInsetsController().getHost().getInputMethodManager(), null /* icProto */);
- }
- SomeArgs args = SomeArgs.obtain();
+ private void dispatchInsetsControlChanged(@NonNull InsetsState insetsState,
+ @NonNull InsetsSourceControl.Array activeControls) {
+ final SomeArgs args = SomeArgs.obtain();
args.arg1 = insetsState;
args.arg2 = activeControls;
mHandler.obtainMessage(MSG_INSETS_CONTROL_CHANGED, args).sendToTarget();
@@ -11289,9 +11300,9 @@
return;
}
// The the parameters from WindowStateResizeItem are already copied.
- final boolean needCopy =
+ final boolean needsCopy =
!isFromResizeItem && (Binder.getCallingPid() == Process.myPid());
- if (needCopy) {
+ if (needsCopy) {
insetsState = new InsetsState(insetsState, true /* copySource */);
frames = new ClientWindowFrames(frames);
mergedConfiguration = new MergedConfiguration(mergedConfiguration);
@@ -11307,10 +11318,35 @@
final boolean isFromInsetsControlChangeItem = mIsFromTransactionItem;
mIsFromTransactionItem = false;
final ViewRootImpl viewAncestor = mViewAncestor.get();
- if (viewAncestor != null) {
- viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls.get());
+ if (viewAncestor == null) {
+ if (isFromInsetsControlChangeItem) {
+ activeControls.release();
+ }
+ return;
}
- // TODO(b/339380439): no need to post if the call is from InsetsControlChangeItem
+ if (insetsState.isSourceOrDefaultVisible(ID_IME, Type.ime())) {
+ ImeTracing.getInstance().triggerClientDump(
+ "ViewRootImpl#dispatchInsetsControlChanged",
+ viewAncestor.getInsetsController().getHost().getInputMethodManager(),
+ null /* icProto */);
+ }
+ // If the UI thread is the same as the current thread that is dispatching
+ // WindowStateInsetsControlChangeItem, then it can run directly.
+ if (isFromInsetsControlChangeItem && viewAncestor.mHandler.getLooper()
+ == ActivityThread.currentActivityThread().getLooper()) {
+ viewAncestor.handleInsetsControlChanged(insetsState, activeControls);
+ return;
+ }
+ // The parameters from WindowStateInsetsControlChangeItem are already copied.
+ final boolean needsCopy =
+ !isFromInsetsControlChangeItem && (Binder.getCallingPid() == Process.myPid());
+ if (needsCopy) {
+ insetsState = new InsetsState(insetsState, true /* copySource */);
+ activeControls = new InsetsSourceControl.Array(
+ activeControls, true /* copyControls */);
+ }
+
+ viewAncestor.dispatchInsetsControlChanged(insetsState, activeControls);
}
@Override
diff --git a/core/java/android/view/ViewStructure.java b/core/java/android/view/ViewStructure.java
index 86e5bea..1af9387 100644
--- a/core/java/android/view/ViewStructure.java
+++ b/core/java/android/view/ViewStructure.java
@@ -90,6 +90,19 @@
public static final String EXTRA_VIRTUAL_STRUCTURE_TYPE =
"android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_TYPE";
+
+ /**
+ * Key used for specifying the version of the view that generated the virtual structure for
+ * itself and its children
+ *
+ * For example, if the virtual structure is generated by a webview of version "104.0.5112.69",
+ * then the value should be "104.0.5112.69"
+ *
+ * @hide
+ */
+ public static final String EXTRA_VIRTUAL_STRUCTURE_VERSION_NUMBER =
+ "android.view.ViewStructure.extra.VIRTUAL_STRUCTURE_VERSION_NUMBER";
+
/**
* Set the identifier for this view.
*
diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
index 12e0814..1fe8180 100644
--- a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
+++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
@@ -302,6 +302,10 @@
}
@Override
+ public void onMagnificationSystemUIConnectionChanged(boolean connected) {
+ }
+
+ @Override
public void onMagnificationChanged(int displayId, @NonNull Region region,
MagnificationConfig config) {
}
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index ab7b226..edf3387 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -169,3 +169,13 @@
description: "Feature flag for declaring system pinch zoom opt-out apis"
bug: "315089687"
}
+
+flag {
+ name: "wait_magnification_system_ui_connection_to_notify_service_connected"
+ namespace: "accessibility"
+ description: "Decide whether AccessibilityService needs to wait until magnification system ui connection is ready to trigger onServiceConnected"
+ bug: "337800504"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java
index 1bd921b..d5398e6 100644
--- a/core/java/android/window/ClientWindowFrames.java
+++ b/core/java/android/window/ClientWindowFrames.java
@@ -56,7 +56,16 @@
public ClientWindowFrames() {
}
- public ClientWindowFrames(ClientWindowFrames other) {
+ public ClientWindowFrames(@NonNull ClientWindowFrames other) {
+ setTo(other);
+ }
+
+ private ClientWindowFrames(@NonNull Parcel in) {
+ readFromParcel(in);
+ }
+
+ /** Updates the current frames to the given frames. */
+ public void setTo(@NonNull ClientWindowFrames other) {
frame.set(other.frame);
displayFrame.set(other.displayFrame);
parentFrame.set(other.parentFrame);
@@ -67,10 +76,6 @@
compatScale = other.compatScale;
}
- private ClientWindowFrames(Parcel in) {
- readFromParcel(in);
- }
-
/** Needed for AIDL out parameters. */
public void readFromParcel(Parcel in) {
frame.readFromParcel(in);
diff --git a/core/java/android/window/SnapshotDrawerUtils.java b/core/java/android/window/SnapshotDrawerUtils.java
index f928f50..4c8bad6 100644
--- a/core/java/android/window/SnapshotDrawerUtils.java
+++ b/core/java/android/window/SnapshotDrawerUtils.java
@@ -77,6 +77,14 @@
private static final String TAG = "SnapshotDrawerUtils";
/**
+ * Used to check if toolkitSetFrameRateReadOnly flag is enabled
+ *
+ * @hide
+ */
+ private static boolean sToolkitSetFrameRateReadOnlyFlagValue =
+ android.view.flags.Flags.toolkitSetFrameRateReadOnly();
+
+ /**
* When creating the starting window, we use the exact same layout flags such that we end up
* with a window with the exact same dimensions etc. However, these flags are not used in layout
* and might cause other side effects so we exclude them.
@@ -439,6 +447,9 @@
layoutParams.setFitInsetsTypes(attrs.getFitInsetsTypes());
layoutParams.setFitInsetsSides(attrs.getFitInsetsSides());
layoutParams.setFitInsetsIgnoringVisibility(attrs.isFitInsetsIgnoringVisibility());
+ if (sToolkitSetFrameRateReadOnlyFlagValue) {
+ layoutParams.setFrameRatePowerSavingsBalanced(false);
+ }
layoutParams.setTitle(title);
layoutParams.inputFeatures |= INPUT_FEATURE_NO_INPUT_CHANNEL;
diff --git a/core/java/com/android/internal/compat/ChangeReporter.java b/core/java/com/android/internal/compat/ChangeReporter.java
index ded142c..d8f1309 100644
--- a/core/java/com/android/internal/compat/ChangeReporter.java
+++ b/core/java/com/android/internal/compat/ChangeReporter.java
@@ -98,7 +98,7 @@
public void reportChange(int uid, long changeId, int state, boolean isLoggableBySdk) {
boolean isAlreadyReported =
checkAndSetIsAlreadyReported(uid, new ChangeReport(changeId, state));
- if (!isAlreadyReported) {
+ if (shouldWriteToStatsLog(isAlreadyReported)) {
FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPATIBILITY_CHANGE_REPORTED, uid,
changeId, state, mSource);
}
@@ -136,14 +136,16 @@
/**
* Returns whether the next report should be logged to FrameworkStatsLog.
*
- * @param uid affected by the change
- * @param changeId the reported change id
- * @param state of the reported change - enabled/disabled/only logged
+ * @param isAlreadyReported is the change already reported
* @return true if the report should be logged
*/
@VisibleForTesting
- boolean shouldWriteToStatsLog(int uid, long changeId, int state) {
- return !isAlreadyReported(uid, new ChangeReport(changeId, state));
+ boolean shouldWriteToStatsLog(boolean isAlreadyReported) {
+ // We don't log for system server
+ if (mSource == SOURCE_SYSTEM_SERVER) return false;
+
+ // Don't log if already reported
+ return !isAlreadyReported;
}
/**
@@ -160,8 +162,6 @@
boolean isAlreadyReported, int state, boolean isLoggableBySdk) {
// If log all bit is on, always return true.
if (mDebugLogAll) return true;
- // If the change has already been reported, do not write.
- if (isAlreadyReported) return false;
// If the flag is turned off or the TAG's logging is forced to debug level with
// `adb setprop log.tag.CompatChangeReporter=DEBUG`, write to debug since the above checks
@@ -224,6 +224,19 @@
return mReportedChanges.getOrDefault(uid, EMPTY_SET).contains(report);
}
+ /**
+ * Returns whether the next report should be logged.
+ *
+ * @param uid affected by the change
+ * @param changeId the reported change id
+ * @param state of the reported change - enabled/disabled/only logged
+ * @return true if the report should be logged
+ */
+ @VisibleForTesting
+ boolean isAlreadyReported(int uid, long changeId, int state) {
+ return isAlreadyReported(uid, new ChangeReport(changeId, state));
+ }
+
private void markAsReported(int uid, ChangeReport report) {
mReportedChanges.computeIfAbsent(uid, NEW_CHANGE_REPORT_SET).add(report);
}
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index 37b7288..42fa6ac 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -135,7 +135,7 @@
new DataSourceParams.Builder()
.setBufferExhaustedPolicy(
DataSourceParams
- .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_STALL_AND_ABORT)
+ .PERFETTO_DS_BUFFER_EXHAUSTED_POLICY_DROP)
.build();
mDataSource.register(params);
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
diff --git a/core/java/com/android/internal/util/NewlineNormalizer.java b/core/java/com/android/internal/util/NewlineNormalizer.java
deleted file mode 100644
index 0104d1f..0000000
--- a/core/java/com/android/internal/util/NewlineNormalizer.java
+++ /dev/null
@@ -1,39 +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.internal.util;
-
-
-import java.util.regex.Pattern;
-
-/**
- * Utility class that replaces consecutive empty lines with single new line.
- * @hide
- */
-public class NewlineNormalizer {
-
- private static final Pattern MULTIPLE_NEWLINES = Pattern.compile("\\v(\\s*\\v)?");
-
- // Private constructor to prevent instantiation
- private NewlineNormalizer() {}
-
- /**
- * Replaces consecutive newlines with a single newline in the input text.
- */
- public static String normalizeNewlines(String text) {
- return MULTIPLE_NEWLINES.matcher(text).replaceAll("\n");
- }
-}
diff --git a/core/java/com/android/internal/util/NotificationBigTextNormalizer.java b/core/java/com/android/internal/util/NotificationBigTextNormalizer.java
new file mode 100644
index 0000000..80d4095
--- /dev/null
+++ b/core/java/com/android/internal/util/NotificationBigTextNormalizer.java
@@ -0,0 +1,123 @@
+/*
+ * 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.internal.util;
+
+
+import android.annotation.NonNull;
+import android.os.Trace;
+
+import java.util.regex.Pattern;
+
+/**
+ * Utility class that normalizes BigText style Notification content.
+ * @hide
+ */
+public class NotificationBigTextNormalizer {
+
+ private static final Pattern MULTIPLE_NEWLINES = Pattern.compile("\\v(\\s*\\v)?");
+ private static final Pattern HORIZONTAL_WHITESPACES = Pattern.compile("\\h+");
+
+ // Private constructor to prevent instantiation
+ private NotificationBigTextNormalizer() {}
+
+ /**
+ * Normalizes the given text by collapsing consecutive new lines into single one and cleaning
+ * up each line by removing zero-width characters, invisible formatting characters, and
+ * collapsing consecutive whitespace into single space.
+ */
+ @NonNull
+ public static String normalizeBigText(@NonNull String text) {
+ try {
+ Trace.beginSection("NotifBigTextNormalizer#normalizeBigText");
+ text = MULTIPLE_NEWLINES.matcher(text).replaceAll("\n");
+ text = HORIZONTAL_WHITESPACES.matcher(text).replaceAll(" ");
+ text = normalizeLines(text);
+ return text;
+ } finally {
+ Trace.endSection();
+ }
+ }
+
+ /**
+ * Normalizes lines in a text by removing zero-width characters, invisible formatting
+ * characters, and collapsing consecutive whitespace into single space.
+ *
+ * <p>
+ * This method processes the input text line by line. It eliminates zero-width
+ * characters (U+200B to U+200D, U+FEFF, U+034F), invisible formatting
+ * characters (U+2060 to U+2065, U+206A to U+206F, U+FFF9 to U+FFFB),
+ * and replaces any sequence of consecutive whitespace characters with a single space.
+ * </p>
+ *
+ * <p>
+ * Additionally, the method trims trailing whitespace from each line and removes any
+ * resulting empty lines.
+ * </p>
+ */
+ @NonNull
+ private static String normalizeLines(@NonNull String text) {
+ String[] lines = text.split("\n");
+ final StringBuilder textSB = new StringBuilder(text.length());
+ for (int i = 0; i < lines.length; i++) {
+ final String line = lines[i];
+ final StringBuilder lineSB = new StringBuilder(line.length());
+ boolean spaceSeen = false;
+ for (int j = 0; j < line.length(); j++) {
+ final char character = line.charAt(j);
+
+ // Skip ZERO WIDTH characters
+ if ((character >= '\u200B' && character <= '\u200D')
+ || character == '\uFEFF' || character == '\u034F') {
+ continue;
+ }
+ // Skip INVISIBLE_FORMATTING_CHARACTERS
+ if ((character >= '\u2060' && character <= '\u2065')
+ || (character >= '\u206A' && character <= '\u206F')
+ || (character >= '\uFFF9' && character <= '\uFFFB')) {
+ continue;
+ }
+
+ if (isSpace(character)) {
+ // eliminate consecutive spaces....
+ if (!spaceSeen) {
+ lineSB.append(" ");
+ }
+ spaceSeen = true;
+ } else {
+ spaceSeen = false;
+ lineSB.append(character);
+ }
+ }
+ // trim line.
+ final String currentLine = lineSB.toString().trim();
+
+ // don't add empty lines after trim.
+ if (currentLine.length() > 0) {
+ if (textSB.length() > 0) {
+ textSB.append("\n");
+ }
+ textSB.append(currentLine);
+ }
+ }
+
+ return textSB.toString();
+ }
+
+ private static boolean isSpace(char ch) {
+ return ch != '\n' && Character.isSpaceChar(ch);
+ }
+}
diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java
index 0f4615a..58bddae 100644
--- a/core/java/com/android/internal/widget/NotificationRowIconView.java
+++ b/core/java/com/android/internal/widget/NotificationRowIconView.java
@@ -59,7 +59,7 @@
@Override
protected void onFinishInflate() {
// If showing the app icon, we don't need background or padding.
- if (Flags.notificationsUseAppIcon()) {
+ if (Flags.notificationsUseAppIcon() || Flags.notificationsUseAppIconInRow()) {
setPadding(0, 0, 0, 0);
setBackground(null);
}
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index d48cdc4..eaff760 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -713,6 +713,19 @@
AudioSystem::getForceUse(static_cast<audio_policy_force_use_t>(usage)));
}
+static jint android_media_AudioSystem_setDeviceAbsoluteVolumeEnabled(JNIEnv *env, jobject thiz,
+ jint device, jstring address,
+ jboolean enabled,
+ jint stream) {
+ const char *c_address = env->GetStringUTFChars(address, nullptr);
+ int state = check_AudioSystem_Command(
+ AudioSystem::setDeviceAbsoluteVolumeEnabled(static_cast<audio_devices_t>(device),
+ c_address, enabled,
+ static_cast<audio_stream_type_t>(stream)));
+ env->ReleaseStringUTFChars(address, c_address);
+ return state;
+}
+
static jint
android_media_AudioSystem_initStreamVolume(JNIEnv *env, jobject thiz, jint stream, jint indexMin, jint indexMax)
{
@@ -3373,6 +3386,7 @@
MAKE_AUDIO_SYSTEM_METHOD(setPhoneState),
MAKE_AUDIO_SYSTEM_METHOD(setForceUse),
MAKE_AUDIO_SYSTEM_METHOD(getForceUse),
+ MAKE_AUDIO_SYSTEM_METHOD(setDeviceAbsoluteVolumeEnabled),
MAKE_AUDIO_SYSTEM_METHOD(initStreamVolume),
MAKE_AUDIO_SYSTEM_METHOD(setStreamVolumeIndex),
MAKE_AUDIO_SYSTEM_METHOD(getStreamVolumeIndex),
diff --git a/core/jni/android_tracing_PerfettoProducer.cpp b/core/jni/android_tracing_PerfettoProducer.cpp
index f8c63c8..f553380 100644
--- a/core/jni/android_tracing_PerfettoProducer.cpp
+++ b/core/jni/android_tracing_PerfettoProducer.cpp
@@ -34,15 +34,17 @@
namespace android {
-void perfettoProducerInit(JNIEnv* env, jclass clazz, int backends) {
+void perfettoProducerInit(JNIEnv* env, jclass clazz, PerfettoBackendTypes backends,
+ uint32_t shmem_size_hint_kb) {
struct PerfettoProducerInitArgs args = PERFETTO_PRODUCER_INIT_ARGS_INIT();
- args.backends = (PerfettoBackendTypes)backends;
+ args.backends = backends;
+ args.shmem_size_hint_kb = shmem_size_hint_kb;
PerfettoProducerInit(args);
}
const JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
- {"nativePerfettoProducerInit", "(I)V", (void*)perfettoProducerInit},
+ {"nativePerfettoProducerInit", "(II)V", (void*)perfettoProducerInit},
};
int register_android_tracing_PerfettoProducer(JNIEnv* env) {
diff --git a/core/jni/android_util_Binder.cpp b/core/jni/android_util_Binder.cpp
index 3006e20..2068bd7 100644
--- a/core/jni/android_util_Binder.cpp
+++ b/core/jni/android_util_Binder.cpp
@@ -1411,10 +1411,8 @@
return JNI_TRUE;
}
- if (err == FAILED_TRANSACTION) {
- env->CallStaticVoidMethod(gBinderOffsets.mClass, gBinderOffsets.mTransactionCallback,
- getpid(), code, flags, err);
- }
+ env->CallStaticVoidMethod(gBinderOffsets.mClass, gBinderOffsets.mTransactionCallback, getpid(),
+ code, flags, err);
if (err == UNKNOWN_TRANSACTION) {
return JNI_FALSE;
diff --git a/core/jni/com_android_internal_content_FileSystemUtils.cpp b/core/jni/com_android_internal_content_FileSystemUtils.cpp
index 31f4e64..d426f12 100644
--- a/core/jni/com_android_internal_content_FileSystemUtils.cpp
+++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp
@@ -88,7 +88,7 @@
ALOGD("Total number of LOAD segments %zu", programHeaders.size());
ALOGD("Size before punching holes st_blocks: %" PRIu64
- ", st_blksize: %ld, st_size: %" PRIu64 "",
+ ", st_blksize: %d, st_size: %" PRIu64 "",
beforePunch.st_blocks, beforePunch.st_blksize,
static_cast<uint64_t>(beforePunch.st_size));
}
@@ -193,7 +193,7 @@
ALOGD("lstat64 failed for filePath %s, error:%d", filePath, errno);
return false;
}
- ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %ld, st_size: %" PRIu64
+ ALOGD("Size after punching holes st_blocks: %" PRIu64 ", st_blksize: %d, st_size: %" PRIu64
"",
afterPunch.st_blocks, afterPunch.st_blksize,
static_cast<uint64_t>(afterPunch.st_size));
@@ -271,7 +271,7 @@
uint64_t blockSize = beforePunch.st_blksize;
IF_ALOGD() {
ALOGD("Extra field length: %hu, Size before punching holes st_blocks: %" PRIu64
- ", st_blksize: %ld, st_size: %" PRIu64 "",
+ ", st_blksize: %d, st_size: %" PRIu64 "",
extraFieldLen, beforePunch.st_blocks, beforePunch.st_blksize,
static_cast<uint64_t>(beforePunch.st_size));
}
@@ -346,7 +346,7 @@
return false;
}
ALOGD("punchHolesInApk:: Size after punching holes st_blocks: %" PRIu64
- ", st_blksize: %ld, st_size: %" PRIu64 "",
+ ", st_blksize: %d, st_size: %" PRIu64 "",
afterPunch.st_blocks, afterPunch.st_blksize,
static_cast<uint64_t>(afterPunch.st_size));
}
diff --git a/core/proto/android/app/appexitinfo.proto b/core/proto/android/app/appexitinfo.proto
index 3abc462..e560a94 100644
--- a/core/proto/android/app/appexitinfo.proto
+++ b/core/proto/android/app/appexitinfo.proto
@@ -20,7 +20,7 @@
package android.app;
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/enums.proto";
+import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
/**
* An android.app.ApplicationExitInfo object.
diff --git a/core/proto/android/app/appstartinfo.proto b/core/proto/android/app/appstartinfo.proto
index d9ed911..c137533 100644
--- a/core/proto/android/app/appstartinfo.proto
+++ b/core/proto/android/app/appstartinfo.proto
@@ -20,7 +20,7 @@
package android.app;
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/enums.proto";
+import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
/**
* An android.app.ApplicationStartInfo object.
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index 4c84944..97f8148 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -21,7 +21,7 @@
import "frameworks/base/core/proto/android/os/powermanager.proto";
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/job/enums.proto";
+import "frameworks/proto_logging/stats/enums/app/job/job_enums.proto";
import "frameworks/proto_logging/stats/enums/telephony/enums.proto";
message BatteryStatsProto {
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index e3a438d..921c41c 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -35,7 +35,7 @@
import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
import "frameworks/base/core/proto/android/util/common.proto";
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/enums.proto";
+import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
option java_multiple_files = true;
diff --git a/core/proto/android/server/jobscheduler.proto b/core/proto/android/server/jobscheduler.proto
index 00127c1..a1e3dc1 100644
--- a/core/proto/android/server/jobscheduler.proto
+++ b/core/proto/android/server/jobscheduler.proto
@@ -31,7 +31,7 @@
import "frameworks/base/core/proto/android/server/statlogger.proto";
import "frameworks/base/core/proto/android/privacy.proto";
import "frameworks/base/core/proto/android/util/quotatracker.proto";
-import "frameworks/proto_logging/stats/enums/app/job/enums.proto";
+import "frameworks/proto_logging/stats/enums/app/job/job_enums.proto";
import "frameworks/proto_logging/stats/enums/server/job/enums.proto";
message JobSchedulerServiceDumpProto {
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index 2f865af..593bbc6 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -26,7 +26,7 @@
import "frameworks/base/core/proto/android/providers/settings.proto";
import "frameworks/base/core/proto/android/server/wirelesschargerdetector.proto";
import "frameworks/base/core/proto/android/privacy.proto";
-import "frameworks/proto_logging/stats/enums/app/enums.proto";
+import "frameworks/proto_logging/stats/enums/app/app_enums.proto";
import "frameworks/proto_logging/stats/enums/os/enums.proto";
import "frameworks/proto_logging/stats/enums/view/enums.proto";
diff --git a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
index 12a42f9..c32fd45 100644
--- a/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
+++ b/core/tests/PlatformCompatFramework/src/com/android/internal/compat/ChangeReporterTest.java
@@ -31,21 +31,21 @@
@Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@Test
- public void testStatsLogOnce() {
+ public void testIsAlreadyReportedOnce() {
ChangeReporter reporter = new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
int myUid = 1022, otherUid = 1023;
long myChangeId = 500L, otherChangeId = 600L;
int myState = ChangeReporter.STATE_ENABLED, otherState = ChangeReporter.STATE_DISABLED;
- assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+ assertFalse(reporter.isAlreadyReported(myUid, myChangeId, myState));
reporter.reportChange(myUid, myChangeId, myState);
// Same report will not be logged again.
- assertFalse(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+ assertTrue(reporter.isAlreadyReported(myUid, myChangeId, myState));
// Other reports will be logged.
- assertTrue(reporter.shouldWriteToStatsLog(otherUid, myChangeId, myState));
- assertTrue(reporter.shouldWriteToStatsLog(myUid, otherChangeId, myState));
- assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, otherState));
+ assertFalse(reporter.isAlreadyReported(otherUid, myChangeId, myState));
+ assertFalse(reporter.isAlreadyReported(myUid, otherChangeId, myState));
+ assertFalse(reporter.isAlreadyReported(myUid, myChangeId, otherState));
}
@Test
@@ -55,15 +55,15 @@
long myChangeId = 500L;
int myState = ChangeReporter.STATE_ENABLED;
- assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+ assertFalse(reporter.isAlreadyReported(myUid, myChangeId, myState));
reporter.reportChange(myUid, myChangeId, myState);
// Same report will not be logged again.
- assertFalse(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+ assertTrue(reporter.isAlreadyReported(myUid, myChangeId, myState));
reporter.resetReportedChanges(myUid);
// Same report will be logged again after reset.
- assertTrue(reporter.shouldWriteToStatsLog(myUid, myChangeId, myState));
+ assertFalse(reporter.isAlreadyReported(myUid, myChangeId, myState));
}
@Test
@@ -196,4 +196,21 @@
// off.
assertTrue(reporter.shouldWriteToDebug(myUid, myChangeId, myDisabledState, false));
}
+
+ @Test
+ public void testDontLogSystemServer() {
+ ChangeReporter systemServerReporter =
+ new ChangeReporter(ChangeReporter.SOURCE_SYSTEM_SERVER);
+
+ // We never log from system server, even if already reported.
+ assertFalse(systemServerReporter.shouldWriteToStatsLog(false));
+ assertFalse(systemServerReporter.shouldWriteToStatsLog(true));
+
+ ChangeReporter unknownSourceReporter =
+ new ChangeReporter(ChangeReporter.SOURCE_UNKNOWN_SOURCE);
+
+ // Otherwise we log if it's not already been reported.
+ assertTrue(unknownSourceReporter.shouldWriteToStatsLog(false));
+ assertFalse(unknownSourceReporter.shouldWriteToStatsLog(true));
+ }
}
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 89c2b3c..b972882 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -128,7 +128,8 @@
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
- 0, // statusBarAppearance
+ 0x555555, // systemBarsAppeareance
+ 0x666666, // topOpaqueSystemBarsAppeareance
true, // ensureStatusBarContrastWhenTransparent
true, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_RESIZEABLE, // resizeMode
@@ -153,7 +154,8 @@
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
- 0, // statusBarAppearance
+ 0x555555, // systemBarsAppeareance
+ 0x666666, // topOpaqueSystemBarsAppeareance
false, // ensureStatusBarContrastWhenTransparent
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
@@ -169,7 +171,8 @@
0x2222222, // colorBackground
0x3333332, // statusBarColor
0x4444442, // navigationBarColor
- 0, // statusBarAppearance
+ 0x5555552, // systemBarsAppeareance
+ 0x6666662, // topOpaqueSystemBarsAppeareance
true, // ensureStatusBarContrastWhenTransparent
true, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_RESIZEABLE, // resizeMode
@@ -200,7 +203,8 @@
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
- 0, // statusBarAppearance
+ 0x555555, // systemBarsAppeareance
+ 0x666666, // topOpaqueSystemBarsAppeareance
false, // ensureStatusBarContrastWhenTransparent
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
@@ -223,7 +227,8 @@
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
- 0, // statusBarAppearance
+ 0x555555, // systemBarsAppeareance
+ 0x666666, // topOpaqueSystemBarsAppeareance
false, // ensureStatusBarContrastWhenTransparent
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
@@ -256,6 +261,8 @@
assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
assertEquals(td1.getSystemBarsAppearance(), td2.getSystemBarsAppearance());
+ assertEquals(td1.getTopOpaqueSystemBarsAppearance(),
+ td2.getTopOpaqueSystemBarsAppearance());
assertEquals(td1.getResizeMode(), td2.getResizeMode());
assertEquals(td1.getMinWidth(), td2.getMinWidth());
assertEquals(td1.getMinHeight(), td2.getMinHeight());
diff --git a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
index 3735274..c7060ad 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ClientTransactionItemTest.java
@@ -23,6 +23,8 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.Activity;
@@ -39,7 +41,6 @@
import android.view.InsetsState;
import android.window.ActivityWindowInfo;
import android.window.ClientWindowFrames;
-import android.window.WindowContext;
import android.window.WindowContextInfo;
import androidx.test.filters.SmallTest;
@@ -73,8 +74,6 @@
@Mock
private IBinder mWindowClientToken;
@Mock
- private WindowContext mWindowContext;
- @Mock
private IWindow mWindow;
// Can't mock final class.
@@ -176,4 +175,17 @@
verify(mWindow).insetsControlChanged(mInsetsState, mActiveControls);
}
+
+ @Test
+ public void testWindowStateInsetsControlChangeItem_executeError() throws RemoteException {
+ doThrow(new RemoteException()).when(mWindow).insetsControlChanged(any(), any());
+
+ mActiveControls = spy(mActiveControls);
+ final WindowStateInsetsControlChangeItem item = WindowStateInsetsControlChangeItem.obtain(
+ mWindow, mInsetsState, mActiveControls);
+ item.mActiveControls = mActiveControls;
+ item.execute(mHandler, mPendingActions);
+
+ verify(mActiveControls).release();
+ }
}
diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java
index 1c12362..98f8b7f 100644
--- a/core/tests/coretests/src/android/text/LayoutTest.java
+++ b/core/tests/coretests/src/android/text/LayoutTest.java
@@ -39,6 +39,7 @@
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.text.Layout.Alignment;
+import android.text.style.ForegroundColorSpan;
import android.text.style.StrikethroughSpan;
import androidx.test.filters.SmallTest;
@@ -933,6 +934,83 @@
expect.that(numBackgroundsFound).isEqualTo(backgroundRectsDrawn);
}
+ @Test
+ @RequiresFlagsEnabled(FLAG_HIGH_CONTRAST_TEXT_SMALL_TEXT_RECT)
+ public void highContrastTextEnabled_testDrawMulticolorText_drawsBlackAndWhiteBackgrounds() {
+ /*
+ Here's what the final render should look like:
+
+ Text | Background
+ ========================
+ al | BW
+ w | WW
+ ei | WW
+ \t; | WW
+ s | BB
+ df | BB
+ s | BB
+ df | BB
+ @ | BB
+ ------------------------
+ */
+
+ mTextPaint.setColor(Color.WHITE);
+
+ mSpannedText.setSpan(
+ // Can't use DKGREY because it is right on the cusp of clamping white
+ new ForegroundColorSpan(0xFF332211),
+ /* start= */ 1,
+ /* end= */ 6,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE
+ );
+ mSpannedText.setSpan(
+ new ForegroundColorSpan(Color.LTGRAY),
+ /* start= */ 8,
+ /* end= */ 11,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE
+ );
+ Layout layout = new StaticLayout(mSpannedText, mTextPaint, mWidth,
+ mAlign, mSpacingMult, mSpacingAdd, /* includePad= */ false);
+
+ final int width = 256;
+ final int height = 256;
+ MockCanvas c = new MockCanvas(width, height);
+ c.setHighContrastTextEnabled(true);
+ layout.draw(
+ c,
+ /* highlightPaths= */ null,
+ /* highlightPaints= */ null,
+ /* selectionPath= */ null,
+ /* selectionPaint= */ null,
+ /* cursorOffsetVertical= */ 0
+ );
+ List<MockCanvas.DrawCommand> drawCommands = c.getDrawCommands();
+ var highlightsDrawn = 0;
+ var numColorChangesWithinOneLine = 1;
+ var textsDrawn = STATIC_LINE_COUNT + numColorChangesWithinOneLine;
+ var backgroundRectsDrawn = STATIC_LINE_COUNT + numColorChangesWithinOneLine;
+ expect.withMessage("wrong number of drawCommands: " + drawCommands)
+ .that(drawCommands.size())
+ .isEqualTo(textsDrawn + backgroundRectsDrawn + highlightsDrawn);
+
+ var backgroundCommands = drawCommands.stream()
+ .filter(it -> it.rect != null)
+ .toList();
+
+ expect.that(backgroundCommands.get(0).paint.getColor()).isEqualTo(Color.BLACK);
+ expect.that(backgroundCommands.get(1).paint.getColor()).isEqualTo(Color.WHITE);
+ expect.that(backgroundCommands.get(2).paint.getColor()).isEqualTo(Color.WHITE);
+ expect.that(backgroundCommands.get(3).paint.getColor()).isEqualTo(Color.WHITE);
+ expect.that(backgroundCommands.get(4).paint.getColor()).isEqualTo(Color.WHITE);
+ expect.that(backgroundCommands.get(5).paint.getColor()).isEqualTo(Color.BLACK);
+ expect.that(backgroundCommands.get(6).paint.getColor()).isEqualTo(Color.BLACK);
+ expect.that(backgroundCommands.get(7).paint.getColor()).isEqualTo(Color.BLACK);
+ expect.that(backgroundCommands.get(8).paint.getColor()).isEqualTo(Color.BLACK);
+ expect.that(backgroundCommands.get(9).paint.getColor()).isEqualTo(Color.BLACK);
+
+ expect.that(backgroundCommands.size()).isEqualTo(backgroundRectsDrawn);
+ }
+
private static final class MockCanvas extends Canvas {
static class DrawCommand {
diff --git a/core/tests/coretests/src/android/text/SpanColorsTest.java b/core/tests/coretests/src/android/text/SpanColorsTest.java
new file mode 100644
index 0000000..3d8d8f9
--- /dev/null
+++ b/core/tests/coretests/src/android/text/SpanColorsTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.text;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Color;
+import android.graphics.drawable.ShapeDrawable;
+import android.platform.test.annotations.Presubmit;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.ImageSpan;
+import android.text.style.UnderlineSpan;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SpanColorsTest {
+ private final TextPaint mWorkPaint = new TextPaint();
+ private SpanColors mSpanColors;
+ private SpannableString mSpannedText;
+
+ @Before
+ public void setup() {
+ mSpanColors = new SpanColors();
+ mSpannedText = new SpannableString("Hello world! This is a test.");
+ mSpannedText.setSpan(new ForegroundColorSpan(Color.RED), 0, 4,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ mSpannedText.setSpan(new ForegroundColorSpan(Color.GREEN), 6, 11,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ mSpannedText.setSpan(new UnderlineSpan(), 5, 10, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ mSpannedText.setSpan(new ImageSpan(new ShapeDrawable()), 1, 2,
+ Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ mSpannedText.setSpan(new ForegroundColorSpan(Color.BLUE), 12, 16,
+ Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+ }
+
+ @Test
+ public void testNoColorFound() {
+ mSpanColors.init(mWorkPaint, mSpannedText, 25, 30); // Beyond the spans
+ assertThat(mSpanColors.getColorAt(27)).isEqualTo(SpanColors.NO_COLOR_FOUND);
+ }
+
+ @Test
+ public void testSingleColorSpan() {
+ mSpanColors.init(mWorkPaint, mSpannedText, 1, 4);
+ assertThat(mSpanColors.getColorAt(3)).isEqualTo(Color.RED);
+ }
+
+ @Test
+ public void testMultipleColorSpans() {
+ mSpanColors.init(mWorkPaint, mSpannedText, 0, mSpannedText.length());
+ assertThat(mSpanColors.getColorAt(2)).isEqualTo(Color.RED);
+ assertThat(mSpanColors.getColorAt(5)).isEqualTo(SpanColors.NO_COLOR_FOUND);
+ assertThat(mSpanColors.getColorAt(8)).isEqualTo(Color.GREEN);
+ assertThat(mSpanColors.getColorAt(13)).isEqualTo(Color.BLUE);
+ }
+}
diff --git a/core/tests/coretests/src/android/util/BinaryXmlTest.java b/core/tests/coretests/src/android/util/BinaryXmlTest.java
index 025e831..da29828 100644
--- a/core/tests/coretests/src/android/util/BinaryXmlTest.java
+++ b/core/tests/coretests/src/android/util/BinaryXmlTest.java
@@ -24,6 +24,8 @@
import static android.util.XmlTest.doVerifyWrite;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
import static org.xmlpull.v1.XmlPullParser.START_TAG;
import android.os.PersistableBundle;
@@ -41,12 +43,15 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
@RunWith(AndroidJUnit4.class)
public class BinaryXmlTest {
+ private static final int MAX_UNSIGNED_SHORT = 65_535;
+
/**
* Verify that we can write and read large numbers of interned
* {@link String} values.
@@ -170,4 +175,49 @@
}
}
}
+
+ @Test
+ public void testAttributeBytes_BinaryDataOverflow() throws Exception {
+ final TypedXmlSerializer out = Xml.newBinarySerializer();
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ out.setOutput(os, StandardCharsets.UTF_8.name());
+
+ final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT + 1];
+ assertThrows(IOException.class,
+ () -> out.attributeBytesHex(/* namespace */ null, /* name */ "attributeBytesHex",
+ testBytes));
+
+ assertThrows(IOException.class,
+ () -> out.attributeBytesBase64(/* namespace */ null, /* name */
+ "attributeBytesBase64", testBytes));
+ }
+
+ @Test
+ public void testAttributeBytesHex_MaximumBinaryData() throws Exception {
+ final TypedXmlSerializer out = Xml.newBinarySerializer();
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ out.setOutput(os, StandardCharsets.UTF_8.name());
+
+ final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT];
+ try {
+ out.attributeBytesHex(/* namespace */ null, /* name */ "attributeBytesHex", testBytes);
+ } catch (Exception e) {
+ fail("testAttributeBytesHex fails with exception: " + e.toString());
+ }
+ }
+
+ @Test
+ public void testAttributeBytesBase64_MaximumBinaryData() throws Exception {
+ final TypedXmlSerializer out = Xml.newBinarySerializer();
+ final ByteArrayOutputStream os = new ByteArrayOutputStream();
+ out.setOutput(os, StandardCharsets.UTF_8.name());
+
+ final byte[] testBytes = new byte[MAX_UNSIGNED_SHORT];
+ try {
+ out.attributeBytesBase64(/* namespace */ null, /* name */ "attributeBytesBase64",
+ testBytes);
+ } catch (Exception e) {
+ fail("testAttributeBytesBase64 fails with exception: " + e.toString());
+ }
+ }
}
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index 07446e7..abe9c8e 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -164,7 +164,7 @@
mActivityRule.runOnUiThread(() -> {
mMovingView.setFrameContentVelocity(1f);
mMovingView.invalidate();
- runAfterDraw(() -> assertEquals(60f, mViewRoot.getLastPreferredFrameRate(), 0f));
+ runAfterDraw(() -> assertEquals(80f, mViewRoot.getLastPreferredFrameRate(), 0f));
});
waitForAfterDraw();
}
@@ -190,7 +190,7 @@
frameLayout.setFrameContentVelocity(1f);
mMovingView.offsetTopAndBottom(100);
frameLayout.invalidate();
- runAfterDraw(() -> assertEquals(60f, mViewRoot.getLastPreferredFrameRate(), 0f));
+ runAfterDraw(() -> assertEquals(80f, mViewRoot.getLastPreferredFrameRate(), 0f));
});
waitForAfterDraw();
}
@@ -435,7 +435,7 @@
runAfterDraw(() -> {
assertEquals(FRAME_RATE_CATEGORY_LOW,
mViewRoot.getLastPreferredFrameRateCategory());
- assertEquals(60f, mViewRoot.getLastPreferredFrameRate());
+ assertEquals(80f, mViewRoot.getLastPreferredFrameRate());
});
});
waitForAfterDraw();
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index a7f8176..94e187a 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -1287,6 +1287,31 @@
}
@Test
+ @RequiresFlagsEnabled(FLAG_TOOLKIT_FRAME_RATE_VIEW_ENABLING_READ_ONLY)
+ public void votePreferredFrameRate_velocityVotedAfterOnDraw() throws Throwable {
+ mView = new View(sContext);
+ double delta = 0.1;
+ float pixelsPerSecond = 1000_000;
+ float expectedFrameRate = 120;
+ attachViewToWindow(mView);
+ sInstrumentation.waitForIdleSync();
+ ViewRootImpl viewRoot = mView.getViewRootImpl();
+ waitForFrameRateCategoryToSettle(mView);
+
+ sInstrumentation.runOnMainSync(() -> {
+ mView.setFrameContentVelocity(pixelsPerSecond);
+ mView.invalidate();
+ assertEquals(0, viewRoot.getPreferredFrameRate(), delta);
+ assertEquals(0, viewRoot.getLastPreferredFrameRate(), delta);
+ runAfterDraw(() -> {
+ assertEquals(expectedFrameRate, viewRoot.getPreferredFrameRate(), delta);
+ assertEquals(expectedFrameRate, viewRoot.getLastPreferredFrameRate(), delta);
+ });
+ });
+ waitForAfterDraw();
+ }
+
+ @Test
public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
ShellIdentityUtils.invokeWithShellPermissions(() -> {
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
index b5c264c..5a6824b 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityServiceConnectionImpl.java
@@ -146,6 +146,10 @@
public void setMagnificationCallbackEnabled(int displayId, boolean enabled) {}
+ public boolean isMagnificationSystemUIConnected() {
+ return false;
+ }
+
public boolean setSoftKeyboardShowMode(int showMode) {
return false;
}
diff --git a/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java b/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java
deleted file mode 100644
index bcdac61..0000000
--- a/core/tests/utiltests/src/com/android/internal/util/NewlineNormalizerTest.java
+++ /dev/null
@@ -1,71 +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.internal.util;
-
-import static junit.framework.Assert.assertEquals;
-
-
-import android.platform.test.annotations.DisabledOnRavenwood;
-import android.platform.test.ravenwood.RavenwoodRule;
-
-import androidx.test.runner.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test for {@link NewlineNormalizer}
- * @hide
- */
-@DisabledOnRavenwood(blockedBy = NewlineNormalizer.class)
-@RunWith(AndroidJUnit4.class)
-public class NewlineNormalizerTest {
-
- @Rule
- public final RavenwoodRule mRavenwood = new RavenwoodRule();
-
- @Test
- public void testEmptyInput() {
- assertEquals("", NewlineNormalizer.normalizeNewlines(""));
- }
-
- @Test
- public void testSingleNewline() {
- assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n"));
- }
-
- @Test
- public void testMultipleConsecutiveNewlines() {
- assertEquals("\n", NewlineNormalizer.normalizeNewlines("\n\n\n\n\n"));
- }
-
- @Test
- public void testNewlinesWithSpacesAndTabs() {
- String input = "Line 1\n \n \t \n\tLine 2";
- // Adjusted expected output to include the tab character
- String expected = "Line 1\n\tLine 2";
- assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
- }
-
- @Test
- public void testMixedNewlineCharacters() {
- String input = "Line 1\r\nLine 2\u000BLine 3\fLine 4\u2028Line 5\u2029Line 6";
- String expected = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6";
- assertEquals(expected, NewlineNormalizer.normalizeNewlines(input));
- }
-}
diff --git a/core/tests/utiltests/src/com/android/internal/util/NotificationBigTextNormalizerTest.java b/core/tests/utiltests/src/com/android/internal/util/NotificationBigTextNormalizerTest.java
new file mode 100644
index 0000000..1f2e24a
--- /dev/null
+++ b/core/tests/utiltests/src/com/android/internal/util/NotificationBigTextNormalizerTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.internal.util;
+
+import static junit.framework.Assert.assertEquals;
+
+
+import android.platform.test.annotations.DisabledOnRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test for {@link NotificationBigTextNormalizer}
+ * @hide
+ */
+@DisabledOnRavenwood(blockedBy = NotificationBigTextNormalizer.class)
+@RunWith(AndroidJUnit4.class)
+public class NotificationBigTextNormalizerTest {
+
+ @Rule
+ public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
+
+ @Test
+ public void testEmptyInput() {
+ assertEquals("", NotificationBigTextNormalizer.normalizeBigText(""));
+ }
+
+ @Test
+ public void testSingleNewline() {
+ assertEquals("", NotificationBigTextNormalizer.normalizeBigText("\n"));
+ }
+
+ @Test
+ public void testMultipleConsecutiveNewlines() {
+ assertEquals("", NotificationBigTextNormalizer.normalizeBigText("\n\n\n\n\n"));
+ }
+
+ @Test
+ public void testNewlinesWithSpacesAndTabs() {
+ String input = "Line 1\n \n \t \n\tLine 2";
+ // Adjusted expected output to include the tab character
+ String expected = "Line 1\nLine 2";
+ assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
+ }
+
+ @Test
+ public void testMixedNewlineCharacters() {
+ String input = "Line 1\r\nLine 2\u000BLine 3\fLine 4\u2028Line 5\u2029Line 6";
+ String expected = "Line 1\nLine 2\nLine 3\nLine 4\nLine 5\nLine 6";
+ assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
+ }
+
+ @Test
+ public void testConsecutiveSpaces() {
+ // Only spaces
+ assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This"
+ + " is a test."));
+ // Zero width characters bw spaces.
+ assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This"
+ + "\u200B \u200B \u200B \u200B \u200B \u200B \u200B \u200Bis\uFEFF \uFEFF \uFEFF"
+ + " \uFEFFa \u034F \u034F \u034F \u034F \u034F \u034Ftest."));
+
+ // Invisible formatting characters bw spaces.
+ assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This"
+ + "\u2061 \u2061 \u2061 \u2061 \u2061 \u2061 \u2061 \u2061is\u206E \u206E \u206E"
+ + " \u206Ea \uFFFB \uFFFB \uFFFB \uFFFB \uFFFB \uFFFBtest."));
+ // Non breakable spaces
+ assertEquals("This is a test.", NotificationBigTextNormalizer.normalizeBigText("This"
+ + "\u00A0\u00A0\u00A0\u00A0\u00A0\u00A0is\u2005 \u2005 \u2005"
+ + " \u2005a\u2005\u2005\u2005 \u2005\u2005\u2005test."));
+ }
+
+ @Test
+ public void testZeroWidthCharRemoval() {
+ // Test each character individually
+ char[] zeroWidthChars = { '\u200B', '\u200C', '\u200D', '\uFEFF', '\u034F' };
+
+ for (char c : zeroWidthChars) {
+ String input = "Test" + c + "string";
+ String expected = "Teststring";
+ assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
+ }
+ }
+
+ @Test
+ public void testWhitespaceReplacement() {
+ assertEquals("This text has horizontal whitespace.",
+ NotificationBigTextNormalizer.normalizeBigText(
+ "This\ttext\thas\thorizontal\twhitespace."));
+ assertEquals("This text has mixed whitespace.",
+ NotificationBigTextNormalizer.normalizeBigText(
+ "This text has \u00A0 mixed\u2009whitespace."));
+ assertEquals("This text has leading and trailing whitespace.",
+ NotificationBigTextNormalizer.normalizeBigText(
+ "\t This text has leading and trailing whitespace. \n"));
+ }
+
+ @Test
+ public void testInvisibleFormattingCharacterRemoval() {
+ // Test each character individually
+ char[] invisibleFormattingChars = {
+ '\u2060', '\u2061', '\u2062', '\u2063', '\u2064', '\u2065',
+ '\u206A', '\u206B', '\u206C', '\u206D', '\u206E', '\u206F',
+ '\uFFF9', '\uFFFA', '\uFFFB'
+ };
+
+ for (char c : invisibleFormattingChars) {
+ String input = "Test " + c + "string";
+ String expected = "Test string";
+ assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
+ }
+ }
+ @Test
+ public void testNonBreakSpaceReplacement() {
+ // Test each character individually
+ char[] nonBreakSpaces = {
+ '\u00A0', '\u1680', '\u2000', '\u2001', '\u2002',
+ '\u2003', '\u2004', '\u2005', '\u2006', '\u2007',
+ '\u2008', '\u2009', '\u200A', '\u202F', '\u205F', '\u3000'
+ };
+
+ for (char c : nonBreakSpaces) {
+ String input = "Test" + c + "string";
+ String expected = "Test string";
+ assertEquals(expected, NotificationBigTextNormalizer.normalizeBigText(input));
+ }
+ }
+}
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index d359a90..3cff915 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -1149,6 +1149,8 @@
/**
* Sets the serial number used for the certificate of the generated key pair.
+ * To ensure compatibility with devices and certificate parsers, the value
+ * should be 20 bytes or shorter (see RFC 5280 section 4.1.2.2).
*
* <p>By default, the serial number is {@code 1}.
*/
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
index 774b212..23dc96c 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java
@@ -245,11 +245,9 @@
isReversedLayout,
parentInfo.getDisplayId(),
isDraggableExpandType,
- getContainerBackgroundColor(
- primaryContainer, DEFAULT_PRIMARY_VEIL_COLOR),
- getContainerBackgroundColor(
- secondaryContainer, DEFAULT_SECONDARY_VEIL_COLOR)
- ));
+ primaryContainer,
+ secondaryContainer)
+ );
}
}
@@ -965,8 +963,10 @@
private final int mDisplayId;
private final boolean mIsReversedLayout;
private final boolean mIsDraggableExpandType;
- private final Color mPrimaryVeilColor;
- private final Color mSecondaryVeilColor;
+ @NonNull
+ private final TaskFragmentContainer mPrimaryContainer;
+ @NonNull
+ private final TaskFragmentContainer mSecondaryContainer;
private final int mDividerWidthPx;
@VisibleForTesting
@@ -979,8 +979,8 @@
boolean isReversedLayout,
int displayId,
boolean isDraggableExpandType,
- @NonNull Color primaryVeilColor,
- @NonNull Color secondaryVeilColor) {
+ @NonNull TaskFragmentContainer primaryContainer,
+ @NonNull TaskFragmentContainer secondaryContainer) {
mConfiguration = configuration;
mDividerAttributes = dividerAttributes;
mDecorSurface = decorSurface;
@@ -989,8 +989,8 @@
mIsReversedLayout = isReversedLayout;
mDisplayId = displayId;
mIsDraggableExpandType = isDraggableExpandType;
- mPrimaryVeilColor = primaryVeilColor;
- mSecondaryVeilColor = secondaryVeilColor;
+ mPrimaryContainer = primaryContainer;
+ mSecondaryContainer = secondaryContainer;
mDividerWidthPx = getDividerWidthPx(dividerAttributes);
}
@@ -1014,8 +1014,8 @@
&& a.mDisplayId == b.mDisplayId
&& a.mIsReversedLayout == b.mIsReversedLayout
&& a.mIsDraggableExpandType == b.mIsDraggableExpandType
- && a.mPrimaryVeilColor.equals(b.mPrimaryVeilColor)
- && a.mSecondaryVeilColor.equals(b.mSecondaryVeilColor);
+ && a.mPrimaryContainer == b.mPrimaryContainer
+ && a.mSecondaryContainer == b.mSecondaryContainer;
}
private static boolean areSameSurfaces(
@@ -1328,8 +1328,12 @@
}
private void showVeils(@NonNull SurfaceControl.Transaction t) {
- t.setColor(mPrimaryVeil, colorToFloatArray(mProperties.mPrimaryVeilColor))
- .setColor(mSecondaryVeil, colorToFloatArray(mProperties.mSecondaryVeilColor))
+ final Color primaryVeilColor = getContainerBackgroundColor(
+ mProperties.mPrimaryContainer, DEFAULT_PRIMARY_VEIL_COLOR);
+ final Color secondaryVeilColor = getContainerBackgroundColor(
+ mProperties.mSecondaryContainer, DEFAULT_SECONDARY_VEIL_COLOR);
+ t.setColor(mPrimaryVeil, colorToFloatArray(primaryVeilColor))
+ .setColor(mSecondaryVeil, colorToFloatArray(secondaryVeilColor))
.setLayer(mDividerSurface, DIVIDER_LAYER)
.setLayer(mPrimaryVeil, VEIL_LAYER)
.setLayer(mSecondaryVeil, VEIL_LAYER)
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
index 4515187..3f67607 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/DividerPresenterTest.java
@@ -166,8 +166,8 @@
false /* isReversedLayout */,
Display.DEFAULT_DISPLAY,
false /* isDraggableExpandType */,
- Color.valueOf(Color.BLACK), /* primaryVeilColor */
- Color.valueOf(Color.GRAY) /* secondaryVeilColor */
+ mockPrimaryContainer,
+ mockSecondaryContainer
);
mDividerPresenter = new DividerPresenter(
diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
index 08e695b..15f8c32 100644
--- a/libs/WindowManager/Shell/aconfig/multitasking.aconfig
+++ b/libs/WindowManager/Shell/aconfig/multitasking.aconfig
@@ -1,3 +1,5 @@
+# proto-file: build/make/tools/aconfig/aconfig_protos/protos/aconfig.proto
+
package: "com.android.wm.shell"
container: "system"
@@ -99,3 +101,13 @@
description: "Enable UI affordances to put other content into a bubble"
bug: "342245211"
}
+
+flag {
+ name: "only_reuse_bubbled_task_when_launched_from_bubble"
+ namespace: "multitasking"
+ description: "Allow reusing bubbled tasks for new activities only when launching from bubbles"
+ bug: "328229865"
+ metadata {
+ purpose: PURPOSE_BUGFIX
+ }
+}
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
index 12d1927..ace2c13 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleExpandedViewPinControllerTest.kt
@@ -26,7 +26,7 @@
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import com.android.internal.protolog.common.ProtoLog
import com.android.wm.shell.R
import com.android.wm.shell.bubbles.BubblePositioner
@@ -35,6 +35,8 @@
import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_IN_DURATION
import com.android.wm.shell.common.bubbles.BaseBubblePinController.Companion.DROP_TARGET_ALPHA_OUT_DURATION
import com.android.wm.shell.common.bubbles.BubbleBarLocation
+import com.android.wm.shell.common.bubbles.BubbleBarLocation.LEFT
+import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
@@ -63,6 +65,9 @@
private lateinit var controller: BubbleExpandedViewPinController
private lateinit var testListener: TestLocationChangeListener
+ private val dropTargetView: View?
+ get() = container.findViewById(R.id.bubble_bar_drop_target)
+
private val pointOnLeft = PointF(100f, 100f)
private val pointOnRight = PointF(1900f, 500f)
@@ -92,13 +97,14 @@
@After
fun tearDown() {
- runOnMainSync { controller.onDragEnd() }
+ getInstrumentation().runOnMainSync { controller.onDragEnd() }
waitForAnimateOut()
}
+ /** Dragging on same side should not show drop target or trigger location changes */
@Test
- fun drag_stayOnSameSide() {
- runOnMainSync {
+ fun drag_stayOnRightSide() {
+ getInstrumentation().runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
controller.onDragEnd()
@@ -106,71 +112,124 @@
waitForAnimateIn()
assertThat(dropTargetView).isNull()
assertThat(testListener.locationChanges).isEmpty()
- assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
+ assertThat(testListener.locationReleases).containsExactly(RIGHT)
}
+ /** Dragging on same side should not show drop target or trigger location changes */
@Test
- fun drag_toLeft() {
- // Drag to left, but don't finish
- runOnMainSync {
+ fun drag_stayOnLeftSide() {
+ getInstrumentation().runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = true)
+ controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ controller.onDragEnd()
+ }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNull()
+ assertThat(testListener.locationChanges).isEmpty()
+ assertThat(testListener.locationReleases).containsExactly(LEFT)
+ }
+
+ /** Drag crosses to the other side. Show drop target and trigger a location change. */
+ @Test
+ fun drag_rightToLeft() {
+ getInstrumentation().runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
}
waitForAnimateIn()
assertThat(dropTargetView).isNotNull()
assertThat(dropTargetView!!.alpha).isEqualTo(1f)
-
- val expectedDropTargetBounds = getExpectedDropTargetBounds(onLeft = true)
- assertThat(dropTargetView!!.layoutParams.width).isEqualTo(expectedDropTargetBounds.width())
- assertThat(dropTargetView!!.layoutParams.height)
- .isEqualTo(expectedDropTargetBounds.height())
-
- assertThat(testListener.locationChanges).containsExactly(BubbleBarLocation.LEFT)
+ assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnLeft())
+ assertThat(testListener.locationChanges).containsExactly(LEFT)
assertThat(testListener.locationReleases).isEmpty()
-
- // Finish the drag
- runOnMainSync { controller.onDragEnd() }
- assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.LEFT)
}
+ /** Drag crosses to the other side. Show drop target and trigger a location change. */
@Test
- fun drag_toLeftAndBackToRight() {
- // Drag to left
- runOnMainSync {
- controller.onDragStart(initialLocationOnLeft = false)
+ fun drag_leftToRight() {
+ getInstrumentation().runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = true)
controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
}
waitForAnimateIn()
+
+ assertThat(dropTargetView).isNotNull()
+ assertThat(dropTargetView!!.alpha).isEqualTo(1f)
+ assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight())
+ assertThat(testListener.locationChanges).containsExactly(RIGHT)
+ assertThat(testListener.locationReleases).isEmpty()
+ }
+
+ /**
+ * Drop target does not initially show on the side that the drag starts. Check that it shows up
+ * after the dragging the view to other side and back to the initial side.
+ */
+ @Test
+ fun drag_rightToLeftToRight() {
+ getInstrumentation().runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = false)
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNull()
+
+ getInstrumentation().runOnMainSync { controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) }
+ waitForAnimateIn()
assertThat(dropTargetView).isNotNull()
- // Drag to right
- runOnMainSync { controller.onDragUpdate(pointOnRight.x, pointOnRight.y) }
- // We have to wait for existing drop target to animate out and new to animate in
+ getInstrumentation().runOnMainSync {
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ }
waitForAnimateOut()
waitForAnimateIn()
-
assertThat(dropTargetView).isNotNull()
assertThat(dropTargetView!!.alpha).isEqualTo(1f)
-
- val expectedDropTargetBounds = getExpectedDropTargetBounds(onLeft = false)
- assertThat(dropTargetView!!.layoutParams.width).isEqualTo(expectedDropTargetBounds.width())
- assertThat(dropTargetView!!.layoutParams.height)
- .isEqualTo(expectedDropTargetBounds.height())
-
- assertThat(testListener.locationChanges)
- .containsExactly(BubbleBarLocation.LEFT, BubbleBarLocation.RIGHT)
+ assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight())
+ assertThat(testListener.locationChanges).containsExactly(LEFT, RIGHT).inOrder()
assertThat(testListener.locationReleases).isEmpty()
-
- // Release the view
- runOnMainSync { controller.onDragEnd() }
- assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
+ /**
+ * Drop target does not initially show on the side that the drag starts. Check that it shows up
+ * after the dragging the view to other side and back to the initial side.
+ */
@Test
- fun drag_toLeftInExclusionRect() {
- runOnMainSync {
+ fun drag_leftToRightToLeft() {
+ getInstrumentation().runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = true)
+ controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNull()
+
+ getInstrumentation().runOnMainSync {
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNotNull()
+
+ getInstrumentation().runOnMainSync { controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) }
+ waitForAnimateOut()
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNotNull()
+ assertThat(dropTargetView!!.alpha).isEqualTo(1f)
+ assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnLeft())
+ assertThat(testListener.locationChanges).containsExactly(RIGHT, LEFT).inOrder()
+ assertThat(testListener.locationReleases).isEmpty()
+ }
+
+ /**
+ * Drag from right to left, but stay in exclusion rect around the dismiss view. Drop target
+ * should not show and location change should not trigger.
+ */
+ @Test
+ fun drag_rightToLeft_inExclusionRect() {
+ getInstrumentation().runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
// Exclusion rect is around the bottom center area of the screen
controller.onDragUpdate(SCREEN_WIDTH / 2f - 50, SCREEN_HEIGHT - 100f)
}
@@ -178,85 +237,212 @@
assertThat(dropTargetView).isNull()
assertThat(testListener.locationChanges).isEmpty()
assertThat(testListener.locationReleases).isEmpty()
-
- runOnMainSync { controller.onDragEnd() }
- assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT)
}
+ /**
+ * Drag from left to right, but stay in exclusion rect around the dismiss view. Drop target
+ * should not show and location change should not trigger.
+ */
@Test
- fun toggleSetDropTargetHidden_dropTargetExists() {
- runOnMainSync {
+ fun drag_leftToRight_inExclusionRect() {
+ getInstrumentation().runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = true)
+ controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ // Exclusion rect is around the bottom center area of the screen
+ controller.onDragUpdate(SCREEN_WIDTH / 2f + 50, SCREEN_HEIGHT - 100f)
+ }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNull()
+ assertThat(testListener.locationChanges).isEmpty()
+ assertThat(testListener.locationReleases).isEmpty()
+ }
+
+ /**
+ * Drag to dismiss target and back to the same side should not cause the drop target to show.
+ */
+ @Test
+ fun drag_rightToDismissToRight() {
+ getInstrumentation().runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ controller.onStuckToDismissTarget()
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNull()
+ assertThat(testListener.locationChanges).isEmpty()
+ assertThat(testListener.locationReleases).isEmpty()
+ }
+
+ /**
+ * Drag to dismiss target and back to the same side should not cause the drop target to show.
+ */
+ @Test
+ fun drag_leftToDismissToLeft() {
+ getInstrumentation().runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = true)
+ controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ controller.onStuckToDismissTarget()
controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
}
waitForAnimateIn()
+ assertThat(dropTargetView).isNull()
+ assertThat(testListener.locationChanges).isEmpty()
+ assertThat(testListener.locationReleases).isEmpty()
+ }
+ /** Drag to dismiss target and other side should show drop target on the other side. */
+ @Test
+ fun drag_rightToDismissToLeft() {
+ getInstrumentation().runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = false)
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ controller.onStuckToDismissTarget()
+ controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNotNull()
+ assertThat(dropTargetView!!.alpha).isEqualTo(1f)
+ assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnLeft())
+
+ assertThat(testListener.locationChanges).containsExactly(LEFT)
+ assertThat(testListener.locationReleases).isEmpty()
+ }
+
+ /** Drag to dismiss target and other side should show drop target on the other side. */
+ @Test
+ fun drag_leftToDismissToRight() {
+ getInstrumentation().runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = true)
+ controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ controller.onStuckToDismissTarget()
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNotNull()
+ assertThat(dropTargetView!!.alpha).isEqualTo(1f)
+ assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight())
+
+ assertThat(testListener.locationChanges).containsExactly(RIGHT)
+ assertThat(testListener.locationReleases).isEmpty()
+ }
+
+ /**
+ * Drag to dismiss should trigger a location change to the initial location, if the current
+ * location is different. And hide the drop target.
+ */
+ @Test
+ fun drag_rightToLeftToDismiss() {
+ getInstrumentation().runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = false)
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ }
+ waitForAnimateIn()
assertThat(dropTargetView).isNotNull()
assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- runOnMainSync { controller.setDropTargetHidden(true) }
+ getInstrumentation().runOnMainSync { controller.onStuckToDismissTarget() }
waitForAnimateOut()
- assertThat(dropTargetView).isNotNull()
assertThat(dropTargetView!!.alpha).isEqualTo(0f)
- runOnMainSync { controller.setDropTargetHidden(false) }
+ assertThat(testListener.locationChanges).containsExactly(LEFT, RIGHT).inOrder()
+ assertThat(testListener.locationReleases).isEmpty()
+ }
+
+ /**
+ * Drag to dismiss should trigger a location change to the initial location, if the current
+ * location is different. And hide the drop target.
+ */
+ @Test
+ fun drag_leftToRightToDismiss() {
+ getInstrumentation().runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = true)
+ controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ }
waitForAnimateIn()
assertThat(dropTargetView).isNotNull()
assertThat(dropTargetView!!.alpha).isEqualTo(1f)
- }
-
- @Test
- fun toggleSetDropTargetHidden_noDropTarget() {
- runOnMainSync { controller.setDropTargetHidden(true) }
+ getInstrumentation().runOnMainSync { controller.onStuckToDismissTarget() }
waitForAnimateOut()
- assertThat(dropTargetView).isNull()
-
- runOnMainSync { controller.setDropTargetHidden(false) }
- waitForAnimateIn()
- assertThat(dropTargetView).isNull()
+ assertThat(dropTargetView!!.alpha).isEqualTo(0f)
+ assertThat(testListener.locationChanges).containsExactly(RIGHT, LEFT).inOrder()
+ assertThat(testListener.locationReleases).isEmpty()
}
+ /** Finishing drag should remove drop target and send location update. */
@Test
- fun onDragEnd_dropTargetExists() {
- runOnMainSync {
+ fun drag_rightToLeftRelease() {
+ getInstrumentation().runOnMainSync {
controller.onDragStart(initialLocationOnLeft = false)
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
}
waitForAnimateIn()
assertThat(dropTargetView).isNotNull()
- runOnMainSync { controller.onDragEnd() }
+ getInstrumentation().runOnMainSync { controller.onDragEnd() }
waitForAnimateOut()
assertThat(dropTargetView).isNull()
+ assertThat(testListener.locationChanges).containsExactly(LEFT)
+ assertThat(testListener.locationReleases).containsExactly(LEFT)
}
+ /** Finishing drag should remove drop target and send location update. */
@Test
- fun onDragEnd_noDropTarget() {
- runOnMainSync { controller.onDragEnd() }
+ fun drag_leftToRightRelease() {
+ getInstrumentation().runOnMainSync {
+ controller.onDragStart(initialLocationOnLeft = true)
+ controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y)
+ controller.onDragUpdate(pointOnRight.x, pointOnRight.y)
+ }
+ waitForAnimateIn()
+ assertThat(dropTargetView).isNotNull()
+
+ getInstrumentation().runOnMainSync { controller.onDragEnd() }
waitForAnimateOut()
assertThat(dropTargetView).isNull()
+ assertThat(testListener.locationChanges).containsExactly(RIGHT)
+ assertThat(testListener.locationReleases).containsExactly(RIGHT)
}
- private val dropTargetView: View?
- get() = container.findViewById(R.id.bubble_bar_drop_target)
-
- private fun getExpectedDropTargetBounds(onLeft: Boolean): Rect =
+ private fun getExpectedDropTargetBoundsOnLeft(): Rect =
Rect().also {
- positioner.getBubbleBarExpandedViewBounds(onLeft, false /* isOveflowExpanded */, it)
+ positioner.getBubbleBarExpandedViewBounds(
+ true /* onLeft */,
+ false /* isOverflowExpanded */,
+ it
+ )
}
- private fun runOnMainSync(runnable: Runnable) {
- InstrumentationRegistry.getInstrumentation().runOnMainSync(runnable)
- }
+ private fun getExpectedDropTargetBoundsOnRight(): Rect =
+ Rect().also {
+ positioner.getBubbleBarExpandedViewBounds(
+ false /* onLeft */,
+ false /* isOverflowExpanded */,
+ it
+ )
+ }
private fun waitForAnimateIn() {
// Advance animator for on-device test
- runOnMainSync { animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_IN_DURATION) }
+ getInstrumentation().runOnMainSync {
+ animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_IN_DURATION)
+ }
}
private fun waitForAnimateOut() {
// Advance animator for on-device test
- runOnMainSync { animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_OUT_DURATION) }
+ getInstrumentation().runOnMainSync {
+ animatorTestRule.advanceTimeBy(DROP_TARGET_ALPHA_OUT_DURATION)
+ }
+ }
+
+ private fun View.bounds(): Rect {
+ return Rect(0, 0, layoutParams.width, layoutParams.height).also { rect ->
+ rect.offsetTo(x.toInt(), y.toInt())
+ }
}
internal class TestLocationChangeListener : BaseBubblePinController.LocationChangeListener {
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
index 6ca6517..dc022b4 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransitionUtil.java
@@ -69,7 +69,7 @@
/** Returns {@code true} if the transition is opening or closing mode. */
public static boolean isOpenOrCloseMode(@TransitionInfo.TransitionMode int mode) {
- return isOpeningMode(mode) || mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK;
+ return isOpeningMode(mode) || isClosingMode(mode);
}
/** Returns {@code true} if the transition is opening mode. */
@@ -77,6 +77,11 @@
return mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT;
}
+ /** Returns {@code true} if the transition is closing mode. */
+ public static boolean isClosingMode(@TransitionInfo.TransitionMode int mode) {
+ return mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK;
+ }
+
/** Returns {@code true} if the transition has a display change. */
public static boolean hasDisplayChange(@NonNull TransitionInfo info) {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index ee740fb..9114c7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -27,6 +27,7 @@
import android.graphics.Rect
import android.graphics.RectF
import android.os.RemoteException
+import android.util.TimeUtils
import android.view.Choreographer
import android.view.Display
import android.view.IRemoteAnimationFinishedCallback
@@ -109,6 +110,7 @@
private val postCommitFlingSpring = SpringForce(SPRING_SCALE)
.setStiffness(SpringForce.STIFFNESS_LOW)
.setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ protected var gestureProgress = 0f
/** Background color to be used during the animation, also see [getBackgroundColor] */
protected var customizedBackgroundColor = 0
@@ -212,6 +214,7 @@
private fun onGestureProgress(backEvent: BackEvent) {
val progress = gestureInterpolator.getInterpolation(backEvent.progress)
+ gestureProgress = progress
currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
val yOffset = getYOffset(currentClosingRect, backEvent.touchY)
currentClosingRect.offset(0f, yOffset)
@@ -257,12 +260,16 @@
}
// kick off spring animation with the current velocity from the pre-commit phase, this
- // affects the scaling of the closing activity during post-commit
+ // affects the scaling of the closing and/or opening activity during post-commit
+ val startVelocity =
+ if (gestureProgress < 0.1f) -DEFAULT_FLING_VELOCITY else -velocity * SPRING_SCALE
val flingAnimation = SpringAnimation(postCommitFlingScale, SPRING_SCALE)
- .setStartVelocity(min(0f, -velocity * SPRING_SCALE))
+ .setStartVelocity(startVelocity.coerceIn(-MAX_FLING_VELOCITY, 0f))
.setStartValue(SPRING_SCALE)
.setSpring(postCommitFlingSpring)
flingAnimation.start()
+ // do an animation-frame immediately to prevent idle frame
+ flingAnimation.doAnimationFrame(choreographer.lastFrameTimeNanos / TimeUtils.NANOS_PER_MS)
val valueAnimator =
ValueAnimator.ofFloat(1f, 0f).setDuration(getPostCommitAnimationDuration())
@@ -292,6 +299,7 @@
enteringTarget?.let {
if (it.leash != null && it.leash.isValid) {
transaction.setCornerRadius(it.leash, 0f)
+ if (!triggerBack) transaction.setAlpha(it.leash, 0f)
it.leash.release()
}
enteringTarget = null
@@ -315,19 +323,22 @@
isLetterboxed = false
enteringHasSameLetterbox = false
lastPostCommitFlingScale = SPRING_SCALE
+ gestureProgress = 0f
}
protected fun applyTransform(
leash: SurfaceControl?,
rect: RectF,
alpha: Float,
- baseTransformation: Transformation? = null
+ baseTransformation: Transformation? = null,
+ flingMode: FlingMode = FlingMode.NO_FLING
) {
if (leash == null || !leash.isValid) return
tempRectF.set(rect)
- if (leash == closingTarget?.leash) {
- lastPostCommitFlingScale = (postCommitFlingScale.value / SPRING_SCALE).coerceIn(
- minimumValue = MAX_FLING_SCALE, maximumValue = lastPostCommitFlingScale
+ if (flingMode != FlingMode.NO_FLING) {
+ lastPostCommitFlingScale = min(
+ postCommitFlingScale.value / SPRING_SCALE,
+ if (flingMode == FlingMode.FLING_BOUNCE) 1f else lastPostCommitFlingScale
)
// apply an additional scale to the closing target to account for fling velocity
tempRectF.scaleCentered(lastPostCommitFlingScale)
@@ -529,14 +540,30 @@
private const val MAX_SCRIM_ALPHA_DARK = 0.8f
private const val MAX_SCRIM_ALPHA_LIGHT = 0.2f
private const val SPRING_SCALE = 100f
- private const val MAX_FLING_SCALE = 0.6f
+ private const val MAX_FLING_VELOCITY = 1000f
+ private const val DEFAULT_FLING_VELOCITY = 120f
+ }
+
+ enum class FlingMode {
+ NO_FLING,
+
+ /**
+ * This is used for the closing target in custom cross-activity back animations. When the
+ * back gesture is flung, the closing target shrinks a bit further with a spring motion.
+ */
+ FLING_SHRINK,
+
+ /**
+ * This is used for the closing and opening target in the default cross-activity back
+ * animation. When the back gesture is flung, the closing and opening targets shrink a
+ * bit further and then bounce back with a spring motion.
+ */
+ FLING_BOUNCE
}
}
// The target will loose focus when alpha == 0, so keep a minimum value for it.
-private fun keepMinimumAlpha(transAlpha: Float): Float {
- return max(transAlpha.toDouble(), 0.005).toFloat()
-}
+private fun keepMinimumAlpha(transAlpha: Float) = max(transAlpha, 0.005f)
private fun isDarkMode(context: Context): Boolean {
return context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
index c4aafea..c738ce5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CustomCrossActivityBackAnimation.kt
@@ -57,7 +57,6 @@
private var enterAnimation: Animation? = null
private var closeAnimation: Animation? = null
private val transformation = Transformation()
- private var gestureProgress = 0f
override val allowEnteringYShift = false
@@ -105,7 +104,6 @@
}
override fun getPreCommitEnteringBaseTransformation(progress: Float): Transformation {
- gestureProgress = progress
transformation.clear()
enterAnimation!!.getTransformationAt(progress * PRE_COMMIT_MAX_PROGRESS, transformation)
return transformation
@@ -134,7 +132,13 @@
if (closingTarget == null || enteringTarget == null) return
val closingProgress = closeAnimation!!.getPostCommitProgress(linearProgress)
- applyTransform(closingTarget!!.leash, currentClosingRect, closingProgress, closeAnimation!!)
+ applyTransform(
+ closingTarget!!.leash,
+ currentClosingRect,
+ closingProgress,
+ closeAnimation!!,
+ FlingMode.FLING_SHRINK
+ )
val enteringProgress = MathUtils.lerp(
gestureProgress * PRE_COMMIT_MAX_PROGRESS,
1f,
@@ -144,7 +148,8 @@
enteringTarget!!.leash,
currentEnteringRect,
enteringProgress,
- enterAnimation!!
+ enterAnimation!!,
+ FlingMode.NO_FLING
)
applyTransaction()
}
@@ -153,11 +158,12 @@
leash: SurfaceControl,
rect: RectF,
progress: Float,
- animation: Animation
+ animation: Animation,
+ flingMode: FlingMode
) {
transformation.clear()
animation.getTransformationAt(progress, transformation)
- applyTransform(leash, rect, transformation.alpha, transformation)
+ applyTransform(leash, rect, transformation.alpha, transformation, flingMode)
}
override fun finishAnimation() {
@@ -166,7 +172,6 @@
enterAnimation?.reset()
enterAnimation = null
transformation.clear()
- gestureProgress = 0f
super.finishAnimation()
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
index 44752fe..3b5eb36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
@@ -43,7 +43,7 @@
Choreographer.getInstance()
) {
- private val postCommitInterpolator = Interpolators.FAST_OUT_SLOW_IN
+ private val postCommitInterpolator = Interpolators.EMPHASIZED
private val enteringStartOffset =
context.resources.getDimension(R.dimen.cross_activity_back_entering_start_offset)
override val allowEnteringYShift = true
@@ -87,17 +87,27 @@
override fun onPostCommitProgress(linearProgress: Float) {
super.onPostCommitProgress(linearProgress)
- val closingAlpha = max(1f - linearProgress * 2, 0f)
+ val closingAlpha = max(1f - linearProgress * 5, 0f)
val progress = postCommitInterpolator.getInterpolation(linearProgress)
currentClosingRect.setInterpolatedRectF(startClosingRect, targetClosingRect, progress)
- applyTransform(closingTarget?.leash, currentClosingRect, closingAlpha)
+ applyTransform(
+ closingTarget?.leash,
+ currentClosingRect,
+ closingAlpha,
+ flingMode = FlingMode.FLING_BOUNCE
+ )
currentEnteringRect.setInterpolatedRectF(startEnteringRect, targetEnteringRect, progress)
- applyTransform(enteringTarget?.leash, currentEnteringRect, 1f)
+ applyTransform(
+ enteringTarget?.leash,
+ currentEnteringRect,
+ 1f,
+ flingMode = FlingMode.FLING_BOUNCE
+ )
applyTransaction()
}
companion object {
- private const val POST_COMMIT_DURATION = 300L
+ private const val POST_COMMIT_DURATION = 450L
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index a51ac63..fa1091c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -150,7 +150,7 @@
draggedObject: MagnetizedObject<*>
) {
isStuckToDismiss = true
- pinController.setDropTargetHidden(true)
+ pinController.onStuckToDismissTarget()
}
override fun onUnstuckFromTarget(
@@ -162,7 +162,6 @@
) {
isStuckToDismiss = false
animationHelper.animateUnstuckFromDismissView(target)
- pinController.setDropTargetHidden(false)
}
override fun onReleasedInTarget(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
index e514f9d..eec2468 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BaseBubblePinController.kt
@@ -38,8 +38,10 @@
*/
abstract class BaseBubblePinController(private val screenSizeProvider: () -> Point) {
+ private var initialLocationOnLeft = false
private var onLeft = false
private var dismissZone: RectF? = null
+ private var stuckToDismissTarget = false
private var screenCenterX = 0
private var listener: LocationChangeListener? = null
private var dropTargetAnimator: ObjectAnimator? = null
@@ -50,6 +52,7 @@
* @param initialLocationOnLeft side of the screen where bubble bar is pinned to
*/
fun onDragStart(initialLocationOnLeft: Boolean) {
+ this.initialLocationOnLeft = initialLocationOnLeft
onLeft = initialLocationOnLeft
screenCenterX = screenSizeProvider.invoke().x / 2
dismissZone = getExclusionRect()
@@ -59,22 +62,33 @@
fun onDragUpdate(x: Float, y: Float) {
if (dismissZone?.contains(x, y) == true) return
- if (onLeft && x > screenCenterX) {
- onLeft = false
- onLocationChange(RIGHT)
- } else if (!onLeft && x < screenCenterX) {
- onLeft = true
- onLocationChange(LEFT)
+ val wasOnLeft = onLeft
+ onLeft = x < screenCenterX
+ if (wasOnLeft != onLeft) {
+ onLocationChange(if (onLeft) LEFT else RIGHT)
+ } else if (stuckToDismissTarget) {
+ // Moved out of the dismiss view back to initial side, if we have a drop target, show it
+ getDropTargetView()?.apply { animateIn() }
}
+ // Make sure this gets cleared
+ stuckToDismissTarget = false
}
- /** Temporarily hide the drop target view */
- fun setDropTargetHidden(hidden: Boolean) {
- val targetView = getDropTargetView() ?: return
- if (hidden) {
- targetView.animateOut()
- } else {
- targetView.animateIn()
+ /** Signal the controller that view has been dragged to dismiss view. */
+ fun onStuckToDismissTarget() {
+ stuckToDismissTarget = true
+ // Notify that location may be reset
+ val shouldResetLocation = onLeft != initialLocationOnLeft
+ if (shouldResetLocation) {
+ onLeft = initialLocationOnLeft
+ listener?.onChange(if (onLeft) LEFT else RIGHT)
+ }
+ getDropTargetView()?.apply {
+ animateOut {
+ if (shouldResetLocation) {
+ updateLocation(if (onLeft) LEFT else RIGHT)
+ }
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index c79eef7e..cd478e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -143,6 +143,10 @@
}
public static boolean handles(TransitionInfo info) {
+ // There is no animation for screen-wake unless we are immediately unlocking.
+ if (info.getType() == WindowManager.TRANSIT_WAKE && !info.isKeyguardGoingAway()) {
+ return false;
+ }
return (info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 6224543..6ade81c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -1591,7 +1591,7 @@
public void setHomeTransitionListener(IHomeTransitionListener listener) {
executeRemoteCallWithTaskPermission(mTransitions, "setHomeTransitionListener",
(transitions) -> {
- transitions.mHomeTransitionObserver.setHomeTransitionListener(mTransitions,
+ transitions.mHomeTransitionObserver.setHomeTransitionListener(transitions,
listener);
});
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 6901e75..37cdbb4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -583,17 +583,20 @@
} else if (ev.getAction() == ACTION_HOVER_MOVE
&& MaximizeMenu.Companion.isMaximizeMenuView(id)) {
decoration.onMaximizeMenuHoverMove(id, ev);
+ mMainHandler.removeCallbacks(mCloseMaximizeWindowRunnable);
} else if (ev.getAction() == ACTION_HOVER_EXIT) {
if (!decoration.isMaximizeMenuActive() && id == R.id.maximize_window) {
decoration.onMaximizeWindowHoverExit();
- } else if (id == R.id.maximize_window || id == R.id.maximize_menu) {
+ } else if (id == R.id.maximize_window
+ || MaximizeMenu.Companion.isMaximizeMenuView(id)) {
// Close menu if not hovering over maximize menu or maximize button after a
// delay to give user a chance to re-enter view or to move from one maximize
// menu view to another.
mMainHandler.postDelayed(mCloseMaximizeWindowRunnable,
CLOSE_MAXIMIZE_MENU_DELAY_MS);
- } else if (MaximizeMenu.Companion.isMaximizeMenuView(id)) {
- decoration.onMaximizeMenuHoverExit(id, ev);
+ if (id != R.id.maximize_window) {
+ decoration.onMaximizeMenuHoverExit(id, ev);
+ }
}
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
index 78f0ef7..4f04901 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeButtonView.kt
@@ -88,7 +88,7 @@
}
fun cancelHoverAnimation() {
- hoverProgressAnimatorSet.removeAllListeners()
+ hoverProgressAnimatorSet.childAnimations.forEach { it.removeAllListeners() }
hoverProgressAnimatorSet.cancel()
progressBar.visibility = View.INVISIBLE
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
index ec20471..7ade987 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/extension/TaskInfo.kt
@@ -23,13 +23,13 @@
val TaskInfo.isTransparentCaptionBarAppearance: Boolean
get() {
- val appearance = taskDescription?.systemBarsAppearance ?: 0
+ val appearance = taskDescription?.topOpaqueSystemBarsAppearance ?: 0
return (appearance and APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND) != 0
}
val TaskInfo.isLightCaptionBarAppearance: Boolean
get() {
- val appearance = taskDescription?.systemBarsAppearance ?: 0
+ val appearance = taskDescription?.topOpaqueSystemBarsAppearance ?: 0
return (appearance and APPEARANCE_LIGHT_CAPTION_BARS) != 0
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
index 285e5b6..51b291c0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeUiEventLoggerTest.kt
@@ -39,7 +39,7 @@
class DesktopModeUiEventLoggerTest : ShellTestCase() {
private lateinit var uiEventLoggerFake: UiEventLoggerFake
private lateinit var logger: DesktopModeUiEventLogger
- private val instanceIdSequence = InstanceIdSequence(10)
+ private val instanceIdSequence = InstanceIdSequence(/* instanceIdMax */ 1 shl 20)
@Before
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 3ca9b57..a731e53 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -228,7 +228,7 @@
public void updateRelayoutParams_freeformAndTransparentAppearance_allowsInputFallthrough() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- taskInfo.taskDescription.setSystemBarsAppearance(
+ taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(
APPEARANCE_TRANSPARENT_CAPTION_BAR_BACKGROUND);
final RelayoutParams relayoutParams = new RelayoutParams();
@@ -246,7 +246,7 @@
public void updateRelayoutParams_freeformButOpaqueAppearance_disallowsInputFallthrough() {
final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
taskInfo.configuration.windowConfiguration.setWindowingMode(WINDOWING_MODE_FREEFORM);
- taskInfo.taskDescription.setSystemBarsAppearance(0);
+ taskInfo.taskDescription.setTopOpaqueSystemBarsAppearance(0);
final RelayoutParams relayoutParams = new RelayoutParams();
DesktopModeWindowDecoration.updateRelayoutParams(
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 5ce990f..7b7ccf5 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -48,7 +48,6 @@
"libgui",
"libui",
"libinput",
- "libnativewindow",
],
header_libs: [
diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp
index f1ee325..eecc741 100644
--- a/libs/input/MouseCursorController.cpp
+++ b/libs/input/MouseCursorController.cpp
@@ -165,6 +165,15 @@
}
}
+void MouseCursorController::setSkipScreenshot(bool skip) {
+ std::scoped_lock lock(mLock);
+ if (mLocked.skipScreenshot == skip) {
+ return;
+ }
+ mLocked.skipScreenshot = skip;
+ updatePointerLocked();
+}
+
void MouseCursorController::reloadPointerResources(bool getAdditionalMouseResources) {
std::scoped_lock lock(mLock);
@@ -352,6 +361,7 @@
mLocked.pointerSprite->setLayer(Sprite::BASE_LAYER_POINTER);
mLocked.pointerSprite->setPosition(mLocked.pointerX, mLocked.pointerY);
mLocked.pointerSprite->setDisplayId(mLocked.viewport.displayId);
+ mLocked.pointerSprite->setSkipScreenshot(mLocked.skipScreenshot);
if (mLocked.pointerAlpha > 0) {
mLocked.pointerSprite->setAlpha(mLocked.pointerAlpha);
diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h
index dc7e8ca..78f6413 100644
--- a/libs/input/MouseCursorController.h
+++ b/libs/input/MouseCursorController.h
@@ -53,6 +53,9 @@
void setDisplayViewport(const DisplayViewport& viewport, bool getAdditionalMouseResources);
void setStylusHoverMode(bool stylusHoverMode);
+ // Set/Unset flag to hide the mouse cursor on the mirrored display
+ void setSkipScreenshot(bool skip);
+
void updatePointerIcon(PointerIconStyle iconId);
void setCustomPointerIcon(const SpriteIcon& icon);
void reloadPointerResources(bool getAdditionalMouseResources);
@@ -94,6 +97,7 @@
PointerIconStyle requestedPointerType;
PointerIconStyle resolvedPointerType;
+ bool skipScreenshot{false};
bool animating{false};
} mLocked GUARDED_BY(mLock);
diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp
index cca1b07..11b27a2 100644
--- a/libs/input/PointerController.cpp
+++ b/libs/input/PointerController.cpp
@@ -286,13 +286,16 @@
mCursorController.setCustomPointerIcon(icon);
}
-void PointerController::setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) {
+void PointerController::setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) {
std::scoped_lock lock(getLock());
- if (skip) {
- mLocked.displaysToSkipScreenshot.insert(displayId);
- } else {
- mLocked.displaysToSkipScreenshot.erase(displayId);
- }
+ mLocked.displaysToSkipScreenshot.insert(displayId);
+ mCursorController.setSkipScreenshot(true);
+}
+
+void PointerController::clearSkipScreenshotFlags() {
+ std::scoped_lock lock(getLock());
+ mLocked.displaysToSkipScreenshot.clear();
+ mCursorController.setSkipScreenshot(false);
}
void PointerController::doInactivityTimeout() {
diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h
index c6430f7..4d1e1d7 100644
--- a/libs/input/PointerController.h
+++ b/libs/input/PointerController.h
@@ -66,7 +66,8 @@
void clearSpots() override;
void updatePointerIcon(PointerIconStyle iconId) override;
void setCustomPointerIcon(const SpriteIcon& icon) override;
- void setSkipScreenshot(ui::LogicalDisplayId displayId, bool skip) override;
+ void setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId displayId) override;
+ void clearSkipScreenshotFlags() override;
virtual void setInactivityTimeout(InactivityTimeout inactivityTimeout);
void doInactivityTimeout();
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index 2dcb1f1..5b00fca 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -162,6 +162,16 @@
};
class PointerControllerTest : public Test {
+private:
+ void loopThread();
+
+ std::atomic<bool> mRunning = true;
+ class MyLooper : public Looper {
+ public:
+ MyLooper() : Looper(false) {}
+ ~MyLooper() = default;
+ };
+
protected:
PointerControllerTest();
~PointerControllerTest();
@@ -173,22 +183,16 @@
std::unique_ptr<MockSpriteController> mSpriteController;
std::shared_ptr<PointerController> mPointerController;
sp<android::gui::WindowInfosListener> mRegisteredListener;
+ sp<MyLooper> mLooper;
private:
- void loopThread();
-
- std::atomic<bool> mRunning = true;
- class MyLooper : public Looper {
- public:
- MyLooper() : Looper(false) {}
- ~MyLooper() = default;
- };
- sp<MyLooper> mLooper;
std::thread mThread;
};
-PointerControllerTest::PointerControllerTest() : mPointerSprite(new NiceMock<MockSprite>),
- mLooper(new MyLooper), mThread(&PointerControllerTest::loopThread, this) {
+PointerControllerTest::PointerControllerTest()
+ : mPointerSprite(new NiceMock<MockSprite>),
+ mLooper(new MyLooper),
+ mThread(&PointerControllerTest::loopThread, this) {
mSpriteController.reset(new NiceMock<MockSpriteController>(mLooper));
mPolicy = new MockPointerControllerPolicyInterface();
@@ -339,7 +343,7 @@
testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
// Marking the display to skip screenshot should update sprite as well
- mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, true);
+ mPointerController->setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId::DEFAULT);
EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(true));
// Update spots to sync state with sprite
@@ -348,13 +352,53 @@
testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
// Reset flag and verify again
- mPointerController->setSkipScreenshot(ui::LogicalDisplayId::DEFAULT, false);
+ mPointerController->clearSkipScreenshotFlags();
EXPECT_CALL(*testSpotSprite, setSkipScreenshot).With(testing::Args<0>(false));
mPointerController->setSpots(&testSpotCoords, testIdToIndex.cbegin(), testIdBits,
ui::LogicalDisplayId::DEFAULT);
testing::Mock::VerifyAndClearExpectations(testSpotSprite.get());
}
+class PointerControllerSkipScreenshotFlagTest
+ : public PointerControllerTest,
+ public testing::WithParamInterface<PointerControllerInterface::ControllerType> {};
+
+TEST_P(PointerControllerSkipScreenshotFlagTest, updatesSkipScreenshotFlag) {
+ sp<MockSprite> testPointerSprite(new NiceMock<MockSprite>);
+ EXPECT_CALL(*mSpriteController, createSprite).WillOnce(Return(testPointerSprite));
+
+ // Create a pointer controller
+ mPointerController =
+ PointerController::create(mPolicy, mLooper, *mSpriteController, GetParam());
+ ensureDisplayViewportIsSet(ui::LogicalDisplayId::DEFAULT);
+
+ // By default skip screenshot flag is not set for the sprite
+ EXPECT_CALL(*testPointerSprite, setSkipScreenshot).With(testing::Args<0>(false));
+
+ // Update pointer to sync state with sprite
+ mPointerController->setPosition(100, 100);
+ testing::Mock::VerifyAndClearExpectations(testPointerSprite.get());
+
+ // Marking the controller to skip screenshot should update pointer sprite
+ mPointerController->setSkipScreenshotFlagForDisplay(ui::LogicalDisplayId::DEFAULT);
+ EXPECT_CALL(*testPointerSprite, setSkipScreenshot).With(testing::Args<0>(true));
+
+ // Update pointer to sync state with sprite
+ mPointerController->move(10, 10);
+ testing::Mock::VerifyAndClearExpectations(testPointerSprite.get());
+
+ // Reset flag and verify again
+ mPointerController->clearSkipScreenshotFlags();
+ EXPECT_CALL(*testPointerSprite, setSkipScreenshot).With(testing::Args<0>(false));
+ mPointerController->move(10, 10);
+ testing::Mock::VerifyAndClearExpectations(testPointerSprite.get());
+}
+
+INSTANTIATE_TEST_SUITE_P(PointerControllerSkipScreenshotFlagTest,
+ PointerControllerSkipScreenshotFlagTest,
+ testing::Values(PointerControllerInterface::ControllerType::MOUSE,
+ PointerControllerInterface::ControllerType::STYLUS));
+
class PointerControllerWindowInfoListenerTest : public Test {};
TEST_F(PointerControllerWindowInfoListenerTest,
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 293c561..d148afd 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -1764,6 +1764,10 @@
public static native int getForceUse(int usage);
/** @hide */
@UnsupportedAppUsage
+ public static native int setDeviceAbsoluteVolumeEnabled(int nativeDeviceType,
+ @NonNull String address, boolean enabled, int streamToDriveAbs);
+ /** @hide */
+ @UnsupportedAppUsage
public static native int initStreamVolume(int stream, int indexMin, int indexMax);
@UnsupportedAppUsage
private static native int setStreamVolumeIndex(int stream, int index, int device);
diff --git a/media/java/android/media/session/MediaController.java b/media/java/android/media/session/MediaController.java
index 70462ef..442ccdc 100644
--- a/media/java/android/media/session/MediaController.java
+++ b/media/java/android/media/session/MediaController.java
@@ -141,10 +141,9 @@
}
try {
return mSessionBinder.sendMediaButton(mContext.getPackageName(), keyEvent);
- } catch (RemoteException e) {
- // System is dead. =(
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
- return false;
}
/**
@@ -155,9 +154,8 @@
public @Nullable PlaybackState getPlaybackState() {
try {
return mSessionBinder.getPlaybackState();
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling getPlaybackState.", e);
- return null;
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -169,9 +167,8 @@
public @Nullable MediaMetadata getMetadata() {
try {
return mSessionBinder.getMetadata();
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling getMetadata.", e);
- return null;
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -185,10 +182,9 @@
try {
ParceledListSlice list = mSessionBinder.getQueue();
return list == null ? null : list.getList();
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling getQueue.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
- return null;
}
/**
@@ -197,10 +193,9 @@
public @Nullable CharSequence getQueueTitle() {
try {
return mSessionBinder.getQueueTitle();
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling getQueueTitle", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
- return null;
}
/**
@@ -209,10 +204,9 @@
public @Nullable Bundle getExtras() {
try {
return mSessionBinder.getExtras();
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling getExtras", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
- return null;
}
/**
@@ -232,9 +226,8 @@
public int getRatingType() {
try {
return mSessionBinder.getRatingType();
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling getRatingType.", e);
- return Rating.RATING_NONE;
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -246,10 +239,9 @@
public long getFlags() {
try {
return mSessionBinder.getFlags();
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling getFlags.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
- return 0;
}
/** Returns the current playback info for this session. */
@@ -271,10 +263,9 @@
public @Nullable PendingIntent getSessionActivity() {
try {
return mSessionBinder.getLaunchPendingIntent();
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling getPendingIntent.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
- return null;
}
/**
@@ -304,8 +295,8 @@
// AppOpsManager usages.
mSessionBinder.setVolumeTo(mContext.getPackageName(), mContext.getOpPackageName(),
value, flags);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling setVolumeTo.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -329,8 +320,8 @@
// AppOpsManager usages.
mSessionBinder.adjustVolume(mContext.getPackageName(), mContext.getOpPackageName(),
direction, flags);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling adjustVolumeBy.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -395,8 +386,8 @@
}
try {
mSessionBinder.sendCommand(mContext.getPackageName(), command, args, cb);
- } catch (RemoteException e) {
- Log.d(TAG, "Dead object in sendCommand.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -409,8 +400,8 @@
if (mPackageName == null) {
try {
mPackageName = mSessionBinder.getPackageName();
- } catch (RemoteException e) {
- Log.d(TAG, "Dead object in getPackageName.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
return mPackageName;
@@ -430,8 +421,8 @@
// Get info from the connected session.
try {
mSessionInfo = mSessionBinder.getSessionInfo();
- } catch (RemoteException e) {
- Log.d(TAG, "Dead object in getSessionInfo.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
if (mSessionInfo == null) {
@@ -454,8 +445,8 @@
if (mTag == null) {
try {
mTag = mSessionBinder.getTag();
- } catch (RemoteException e) {
- Log.d(TAG, "Dead object in getTag.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
return mTag;
@@ -485,8 +476,8 @@
try {
mSessionBinder.registerCallback(mContext.getPackageName(), mCbStub);
mCbRegistered = true;
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in registerCallback", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
}
@@ -504,8 +495,8 @@
if (mCbRegistered && mCallbacks.size() == 0) {
try {
mSessionBinder.unregisterCallback(mCbStub);
- } catch (RemoteException e) {
- Log.e(TAG, "Dead object in removeCallbackLocked");
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
mCbRegistered = false;
}
@@ -641,8 +632,8 @@
public void prepare() {
try {
mSessionBinder.prepare(mContext.getPackageName());
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling prepare.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -665,8 +656,8 @@
}
try {
mSessionBinder.prepareFromMediaId(mContext.getPackageName(), mediaId, extras);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling prepare(" + mediaId + ").", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -691,8 +682,8 @@
}
try {
mSessionBinder.prepareFromSearch(mContext.getPackageName(), query, extras);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling prepare(" + query + ").", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -715,8 +706,8 @@
}
try {
mSessionBinder.prepareFromUri(mContext.getPackageName(), uri, extras);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling prepare(" + uri + ").", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -726,8 +717,8 @@
public void play() {
try {
mSessionBinder.play(mContext.getPackageName());
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling play.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -745,8 +736,8 @@
}
try {
mSessionBinder.playFromMediaId(mContext.getPackageName(), mediaId, extras);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling play(" + mediaId + ").", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -767,8 +758,8 @@
}
try {
mSessionBinder.playFromSearch(mContext.getPackageName(), query, extras);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling play(" + query + ").", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -786,8 +777,8 @@
}
try {
mSessionBinder.playFromUri(mContext.getPackageName(), uri, extras);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling play(" + uri + ").", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -798,8 +789,8 @@
public void skipToQueueItem(long id) {
try {
mSessionBinder.skipToQueueItem(mContext.getPackageName(), id);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling skipToItem(" + id + ").", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -810,8 +801,8 @@
public void pause() {
try {
mSessionBinder.pause(mContext.getPackageName());
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling pause.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -822,8 +813,8 @@
public void stop() {
try {
mSessionBinder.stop(mContext.getPackageName());
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling stop.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -835,8 +826,8 @@
public void seekTo(long pos) {
try {
mSessionBinder.seekTo(mContext.getPackageName(), pos);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling seekTo.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -847,8 +838,8 @@
public void fastForward() {
try {
mSessionBinder.fastForward(mContext.getPackageName());
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling fastForward.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -858,8 +849,8 @@
public void skipToNext() {
try {
mSessionBinder.next(mContext.getPackageName());
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling next.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -870,8 +861,8 @@
public void rewind() {
try {
mSessionBinder.rewind(mContext.getPackageName());
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling rewind.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -881,8 +872,8 @@
public void skipToPrevious() {
try {
mSessionBinder.previous(mContext.getPackageName());
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling previous.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -896,8 +887,8 @@
public void setRating(Rating rating) {
try {
mSessionBinder.rate(mContext.getPackageName(), rating);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling rate.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -914,8 +905,8 @@
}
try {
mSessionBinder.setPlaybackSpeed(mContext.getPackageName(), speed);
- } catch (RemoteException e) {
- Log.wtf(TAG, "Error calling setPlaybackSpeed.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
@@ -949,8 +940,8 @@
}
try {
mSessionBinder.sendCustomAction(mContext.getPackageName(), action, args);
- } catch (RemoteException e) {
- Log.d(TAG, "Dead object in sendCustomAction.", e);
+ } catch (RemoteException ex) {
+ throw ex.rethrowFromSystemServer();
}
}
}
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index a33e225..055ccbc 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -27,6 +27,7 @@
field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String ACTION_REQUIRE_UNLOCK_FOR_NFC = "android.nfc.action.REQUIRE_UNLOCK_FOR_NFC";
field @FlaggedApi("android.nfc.enable_nfc_mainline") @RequiresPermission(android.Manifest.permission.SHOW_CUSTOMIZED_RESOLVER) public static final String ACTION_SHOW_NFC_RESOLVER = "android.nfc.action.SHOW_NFC_RESOLVER";
field @FlaggedApi("android.nfc.enable_nfc_mainline") public static final String EXTRA_RESOLVE_INFOS = "android.nfc.extra.RESOLVE_INFOS";
+ field @FlaggedApi("android.nfc.nfc_set_default_disc_tech") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public static final int FLAG_SET_DEFAULT_TECH = 1073741824; // 0x40000000
field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int MESSAGE_TYPE_COMMAND = 1; // 0x1
field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_FAILED = 3; // 0x3
field @FlaggedApi("android.nfc.nfc_vendor_cmd") public static final int SEND_VENDOR_NCI_STATUS_MESSAGE_CORRUPTED = 2; // 0x2
diff --git a/nfc/java/android/nfc/NfcAdapter.java b/nfc/java/android/nfc/NfcAdapter.java
index 698df28..1dfc81e 100644
--- a/nfc/java/android/nfc/NfcAdapter.java
+++ b/nfc/java/android/nfc/NfcAdapter.java
@@ -340,7 +340,8 @@
public static final int FLAG_READER_NFC_BARCODE = 0x10;
/** @hide */
- @IntDef(flag = true, prefix = {"FLAG_READER_"}, value = {
+ @IntDef(flag = true, value = {
+ FLAG_SET_DEFAULT_TECH,
FLAG_READER_KEEP,
FLAG_READER_DISABLE,
FLAG_READER_NFC_A,
@@ -438,7 +439,8 @@
public static final int FLAG_USE_ALL_TECH = 0xff;
/** @hide */
- @IntDef(flag = true, prefix = {"FLAG_LISTEN_"}, value = {
+ @IntDef(flag = true, value = {
+ FLAG_SET_DEFAULT_TECH,
FLAG_LISTEN_KEEP,
FLAG_LISTEN_DISABLE,
FLAG_LISTEN_NFC_PASSIVE_A,
@@ -449,6 +451,18 @@
public @interface ListenTechnology {}
/**
+ * Flag used in {@link #setDiscoveryTechnology(Activity, int, int)}.
+ * <p>
+ * Setting this flag changes the default listen or poll tech.
+ * Only available to privileged apps.
+ * @hide
+ */
+ @SystemApi
+ @FlaggedApi(Flags.FLAG_NFC_SET_DEFAULT_DISC_TECH)
+ @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+ public static final int FLAG_SET_DEFAULT_TECH = 0x40000000;
+
+ /**
* @hide
* @removed
*/
@@ -1874,14 +1888,6 @@
public void setDiscoveryTechnology(@NonNull Activity activity,
@PollTechnology int pollTechnology, @ListenTechnology int listenTechnology) {
- // A special treatment of the _KEEP flags
- if ((listenTechnology & FLAG_LISTEN_KEEP) != 0) {
- listenTechnology = -1;
- }
- if ((pollTechnology & FLAG_READER_KEEP) != 0) {
- pollTechnology = -1;
- }
-
if (listenTechnology == FLAG_LISTEN_DISABLE) {
synchronized (sLock) {
if (!sHasNfcFeature) {
@@ -1901,7 +1907,25 @@
}
}
}
- mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology);
+ /*
+ * Privileged FLAG to set technology mask for all data processed by NFC controller
+ * Note: Use with caution! The app is responsible for ensuring that the discovery
+ * technology mask is returned to default.
+ * Note: FLAG_USE_ALL_TECH used with _KEEP flags will reset the technolody to android default
+ */
+ if (Flags.nfcSetDefaultDiscTech()
+ && ((pollTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH
+ || (listenTechnology & FLAG_SET_DEFAULT_TECH) == FLAG_SET_DEFAULT_TECH)) {
+ Binder token = new Binder();
+ try {
+ NfcAdapter.sService.updateDiscoveryTechnology(token,
+ pollTechnology, listenTechnology);
+ } catch (RemoteException e) {
+ attemptDeadServiceRecovery(e);
+ }
+ } else {
+ mNfcActivityManager.setDiscoveryTech(activity, pollTechnology, listenTechnology);
+ }
}
/**
diff --git a/nfc/java/android/nfc/cardemulation/PollingFrame.java b/nfc/java/android/nfc/cardemulation/PollingFrame.java
index 4c76fb0..5dcc84c 100644
--- a/nfc/java/android/nfc/cardemulation/PollingFrame.java
+++ b/nfc/java/android/nfc/cardemulation/PollingFrame.java
@@ -16,7 +16,6 @@
package android.nfc.cardemulation;
-import android.annotation.DurationMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -33,13 +32,13 @@
/**
* Polling Frames represent data about individual frames of an NFC polling loop. These frames will
- * be deliverd to subclasses of {@link HostApduService} that have registered filters with
- * {@link CardEmulation#registerPollingLoopFilterForService(ComponentName, String)} that match a
- * given frame in a loop and will be delivered through calls to
+ * be delivered to subclasses of {@link HostApduService} that have registered filters with
+ * {@link CardEmulation#registerPollingLoopFilterForService(ComponentName, String, boolean)} that
+ * match a given frame in a loop and will be delivered through calls to
* {@link HostApduService#processPollingFrames(List)}.
*/
@FlaggedApi(android.nfc.Flags.FLAG_NFC_READ_POLLING_LOOP)
-public final class PollingFrame implements Parcelable{
+public final class PollingFrame implements Parcelable {
/**
* @hide
@@ -146,7 +145,6 @@
private final int mType;
private final byte[] mData;
private final int mGain;
- @DurationMillisLong
private final long mTimestamp;
private boolean mTriggeredAutoTransact;
@@ -179,18 +177,18 @@
* @param type the type of the frame
* @param data a byte array of the data contained in the frame
* @param gain the vendor-specific gain of the field
- * @param timestampMillis the timestamp in millisecones
+ * @param timestampMicros the timestamp in microseconds
* @param triggeredAutoTransact whether or not this frame triggered the device to start a
* transaction automatically
*
* @hide
*/
public PollingFrame(@PollingFrameType int type, @Nullable byte[] data,
- int gain, @DurationMillisLong long timestampMillis, boolean triggeredAutoTransact) {
+ int gain, long timestampMicros, boolean triggeredAutoTransact) {
mType = type;
mData = data == null ? new byte[0] : data;
mGain = gain;
- mTimestamp = timestampMillis;
+ mTimestamp = timestampMicros;
mTriggeredAutoTransact = triggeredAutoTransact;
}
@@ -198,11 +196,11 @@
* Returns the type of frame for this polling loop frame.
* The possible return values are:
* <ul>
- * <li>{@link POLLING_LOOP_TYPE_ON}</li>
- * <li>{@link POLLING_LOOP_TYPE_OFF}</li>
- * <li>{@link POLLING_LOOP_TYPE_A}</li>
- * <li>{@link POLLING_LOOP_TYPE_B}</li>
- * <li>{@link POLLING_LOOP_TYPE_F}</li>
+ * <li>{@link #POLLING_LOOP_TYPE_ON}</li>
+ * <li>{@link #POLLING_LOOP_TYPE_OFF}</li>
+ * <li>{@link #POLLING_LOOP_TYPE_A}</li>
+ * <li>{@link #POLLING_LOOP_TYPE_B}</li>
+ * <li>{@link #POLLING_LOOP_TYPE_F}</li>
* </ul>
*/
public @PollingFrameType int getType() {
@@ -226,12 +224,12 @@
}
/**
- * Returns the timestamp of when the polling loop frame was observed in milliseconds. These
- * timestamps are relative and not absolute and should only be used for comparing the timing of
- * frames relative to each other.
- * @return the timestamp in milliseconds
+ * Returns the timestamp of when the polling loop frame was observed, in microseconds. These
+ * timestamps are relative and should only be used for comparing the timing of frames relative
+ * to each other.
+ * @return the timestamp in microseconds
*/
- public @DurationMillisLong long getTimestamp() {
+ public long getTimestamp() {
return mTimestamp;
}
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index cb2a48c..b242a76 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -101,3 +101,12 @@
description: "Enable nfc state change API"
bug: "319934052"
}
+
+flag {
+ name: "nfc_set_default_disc_tech"
+ is_exported: true
+ namespace: "nfc"
+ description: "Flag for NFC set default disc tech API"
+ bug: "321311407"
+}
+
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 9c8ec3b..3022fa0 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/ktx/CredentialKtx.kt
@@ -27,6 +27,7 @@
import android.credentials.selection.Entry
import android.credentials.selection.GetCredentialProviderData
import android.graphics.drawable.Drawable
+import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import androidx.activity.result.IntentSenderRequest
@@ -227,26 +228,31 @@
* 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.
*/
fun retrieveEntryBiometricRequest(
entry: Entry,
- hintPrefix: String,
+ hintPrefix: String
): BiometricRequestInfo? {
- // TODO(b/326243754) : When available, use the official jetpack structured type
- val allowedAuthenticators: Int? = entry.slice.items.firstOrNull {
- it.hasHint(hintPrefix + "SLICE_HINT_ALLOWED_AUTHENTICATORS")
- }?.int
+ // TODO(b/326243754) : When available, use the official jetpack structured typLo
+ val biometricPromptDataBundleKey = "SLICE_HINT_BIOMETRIC_PROMPT_DATA"
+ val biometricPromptDataBundle: Bundle = entry.slice.items.firstOrNull {
+ it.hasHint(hintPrefix + biometricPromptDataBundleKey)
+ }?.bundle ?: return null
+
+ val allowedAuthConstantKey = "androidx.credentials.provider.BUNDLE_HINT_ALLOWED_AUTHENTICATORS"
+ val cryptoOpIdKey = "androidx.credentials.provider.BUNDLE_HINT_CRYPTO_OP_ID"
+
+ if (!biometricPromptDataBundle.containsKey(allowedAuthConstantKey)) {
+ return null
+ }
+
+ val allowedAuthenticators: Int = biometricPromptDataBundle.getInt(allowedAuthConstantKey)
// This is optional and does not affect validating the biometric flow in any case
- 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)
- }
- return null
+ val opId: Long? = if (biometricPromptDataBundle.containsKey(cryptoOpIdKey))
+ biometricPromptDataBundle.getLong(cryptoOpIdKey) else null
+
+ return BiometricRequestInfo(opId = opId, allowedAuthenticators = allowedAuthenticators)
}
val Slice.credentialEntry: CredentialEntry?
diff --git a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/BiometricRequestInfo.kt b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/BiometricRequestInfo.kt
index 486cfe7..fe4bead 100644
--- a/packages/CredentialManager/shared/src/com/android/credentialmanager/model/BiometricRequestInfo.kt
+++ b/packages/CredentialManager/shared/src/com/android/credentialmanager/model/BiometricRequestInfo.kt
@@ -23,6 +23,6 @@
* null.
*/
data class BiometricRequestInfo(
- val opId: Int? = null,
+ val opId: Long? = null,
val allowedAuthenticators: Int
)
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 7bc25ed..894d5ef 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -135,16 +135,18 @@
Log.w(Constants.LOG_TAG, "Unexpected biometric result exists when " +
"autoSelect is preferred.")
}
- // TODO(b/333445754) : Decide whether to propagate info on prompt launch
+ // TODO(b/333445754) : Change the fm option to false in qpr after discussion
if (biometricState.biometricResult != null) {
entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_RESULT,
biometricState.biometricResult.biometricAuthenticationResult
.authenticationType)
+ entryIntent?.putExtra(Constants.BIOMETRIC_FRAMEWORK_OPTION, true)
} else if (biometricState.biometricError != null){
entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_ERROR_CODE,
biometricState.biometricError.errorCode)
entryIntent?.putExtra(Constants.BIOMETRIC_AUTH_ERROR_MESSAGE,
biometricState.biometricError.errorMessage)
+ entryIntent?.putExtra(Constants.BIOMETRIC_FRAMEWORK_OPTION, true)
}
}
val intentSenderRequest = IntentSenderRequest.Builder(pendingIntent)
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
index b43b5f3..c35721c 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt
@@ -38,7 +38,6 @@
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
/**
* Aggregates common display information used for the Biometric Flow.
@@ -121,11 +120,11 @@
getBiometricCancellationSignal: () -> CancellationSignal,
getRequestDisplayInfo: RequestDisplayInfo? = null,
getProviderInfoList: List<ProviderInfo>? = null,
- getProviderDisplayInfo: ProviderDisplayInfo? = null,
-) {
+ getProviderDisplayInfo: ProviderDisplayInfo? = null
+): Boolean {
if (getBiometricPromptState() != BiometricPromptState.INACTIVE) {
// Screen is already up, do not re-launch
- return
+ return false
}
onBiometricPromptStateChange(BiometricPromptState.PENDING)
val biometricDisplayInfo = validateAndRetrieveBiometricGetDisplayInfo(
@@ -137,7 +136,7 @@
if (biometricDisplayInfo == null) {
onBiometricFailureFallback(BiometricFlowType.GET)
- return
+ return false
}
val callback: BiometricPrompt.AuthenticationCallback =
@@ -146,7 +145,7 @@
getBiometricPromptState)
Log.d(TAG, "The BiometricPrompt API call begins for Get.")
- runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
+ return runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
onBiometricFailureFallback, BiometricFlowType.GET, onCancelFlowAndFinish,
getBiometricCancellationSignal)
}
@@ -169,11 +168,11 @@
getBiometricCancellationSignal: () -> CancellationSignal,
createRequestDisplayInfo: com.android.credentialmanager.createflow
.RequestDisplayInfo? = null,
- createProviderInfo: EnabledProviderInfo? = null,
-) {
+ createProviderInfo: EnabledProviderInfo? = null
+): Boolean {
if (getBiometricPromptState() != BiometricPromptState.INACTIVE) {
// Screen is already up, do not re-launch
- return
+ return false
}
onBiometricPromptStateChange(BiometricPromptState.PENDING)
val biometricDisplayInfo = validateAndRetrieveBiometricCreateDisplayInfo(
@@ -184,7 +183,7 @@
if (biometricDisplayInfo == null) {
onBiometricFailureFallback(BiometricFlowType.CREATE)
- return
+ return false
}
val callback: BiometricPrompt.AuthenticationCallback =
@@ -193,7 +192,7 @@
getBiometricPromptState)
Log.d(TAG, "The BiometricPrompt API call begins for Create.")
- runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
+ return runBiometricFlow(context, biometricDisplayInfo, callback, openMoreOptionsPage,
onBiometricFailureFallback, BiometricFlowType.CREATE, onCancelFlowAndFinish,
getBiometricCancellationSignal)
}
@@ -206,19 +205,19 @@
* only device credentials are requested.
*/
private fun runBiometricFlow(
- context: Context,
- biometricDisplayInfo: BiometricDisplayInfo,
- callback: BiometricPrompt.AuthenticationCallback,
- openMoreOptionsPage: () -> Unit,
- onBiometricFailureFallback: (BiometricFlowType) -> Unit,
- biometricFlowType: BiometricFlowType,
- onCancelFlowAndFinish: () -> Unit,
- getBiometricCancellationSignal: () -> CancellationSignal,
-) {
+ context: Context,
+ biometricDisplayInfo: BiometricDisplayInfo,
+ callback: BiometricPrompt.AuthenticationCallback,
+ openMoreOptionsPage: () -> Unit,
+ onBiometricFailureFallback: (BiometricFlowType) -> Unit,
+ biometricFlowType: BiometricFlowType,
+ onCancelFlowAndFinish: () -> Unit,
+ getBiometricCancellationSignal: () -> CancellationSignal
+): Boolean {
try {
if (!canCallBiometricPrompt(biometricDisplayInfo, context)) {
onBiometricFailureFallback(biometricFlowType)
- return
+ return false
}
val biometricPrompt = setupBiometricPrompt(context, biometricDisplayInfo,
@@ -231,7 +230,7 @@
val cryptoOpId = getCryptoOpId(biometricDisplayInfo)
if (cryptoOpId != null) {
biometricPrompt.authenticate(
- BiometricPrompt.CryptoObject(cryptoOpId.toLong()),
+ BiometricPrompt.CryptoObject(cryptoOpId),
cancellationSignal, executor, callback)
} else {
biometricPrompt.authenticate(cancellationSignal, executor, callback)
@@ -239,10 +238,12 @@
} catch (e: IllegalArgumentException) {
Log.w(TAG, "Calling the biometric prompt API failed with: /n${e.localizedMessage}\n")
onBiometricFailureFallback(biometricFlowType)
+ return false
}
+ return true
}
-private fun getCryptoOpId(biometricDisplayInfo: BiometricDisplayInfo): Int? {
+private fun getCryptoOpId(biometricDisplayInfo: BiometricDisplayInfo): Long? {
return biometricDisplayInfo.biometricRequestInfo.opId
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
index 3c80113..cb089ad 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
@@ -22,9 +22,12 @@
const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
"androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED"
const val IS_AUTO_SELECTED_KEY = "IS_AUTO_SELECTED"
- // TODO(b/333445772) : Qualify error codes fully for propagation
- const val BIOMETRIC_AUTH_RESULT = "BIOMETRIC_AUTH_RESULT"
- const val BIOMETRIC_AUTH_ERROR_CODE = "BIOMETRIC_AUTH_ERROR_CODE"
- const val BIOMETRIC_AUTH_ERROR_MESSAGE = "BIOMETRIC_AUTH_ERROR_MESSAGE"
+ const val BIOMETRIC_AUTH_RESULT = "androidx.credentials.provider.BIOMETRIC_AUTH_RESULT"
+ const val BIOMETRIC_AUTH_ERROR_CODE =
+ "androidx.credentials.provider.BIOMETRIC_AUTH_ERROR_CODE"
+ const val BIOMETRIC_AUTH_ERROR_MESSAGE =
+ "androidx.credentials.provider.BIOMETRIC_AUTH_ERROR_MESSAGE"
+ const val BIOMETRIC_FRAMEWORK_OPTION =
+ "androidx.credentials.provider.BIOMETRIC_FRAMEWORK_OPTION"
}
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
index 7d61f73..4993a1f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateCredentialComponents.kt
@@ -123,7 +123,8 @@
onBiometricPromptStateChange =
viewModel::onBiometricPromptStateChange,
getBiometricCancellationSignal =
- viewModel::getBiometricCancellationSignal
+ viewModel::getBiometricCancellationSignal,
+ onLog = { viewModel.logUiEvent(it) },
)
CreateScreenState.MORE_OPTIONS_SELECTION_ONLY -> MoreOptionsSelectionCard(
requestDisplayInfo = createCredentialUiState.requestDisplayInfo,
@@ -642,12 +643,13 @@
getBiometricPromptState: () -> BiometricPromptState,
onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
getBiometricCancellationSignal: () -> CancellationSignal,
+ onLog: @Composable (UiEventEnum) -> Unit
) {
if (biometricEntry == null) {
fallbackToOriginalFlow(BiometricFlowType.CREATE)
return
}
- runBiometricFlowForCreate(
+ val biometricFlowCalled = runBiometricFlowForCreate(
biometricEntry = biometricEntry,
context = LocalContext.current,
openMoreOptionsPage = onMoreOptionSelected,
@@ -659,6 +661,9 @@
createProviderInfo = enabledProviderInfo,
onBiometricFailureFallback = fallbackToOriginalFlow,
onIllegalStateAndFinish = onIllegalScreenStateAndFinish,
- getBiometricCancellationSignal = getBiometricCancellationSignal,
+ getBiometricCancellationSignal = getBiometricCancellationSignal
)
+ if (biometricFlowCalled) {
+ onLog(CreateCredentialEvent.CREDMAN_CREATE_CRED_BIOMETRIC_FLOW_LAUNCHED)
+ }
}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index ba61b90..517ad00 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -166,7 +166,8 @@
onBiometricPromptStateChange =
viewModel::onBiometricPromptStateChange,
getBiometricCancellationSignal =
- viewModel::getBiometricCancellationSignal
+ viewModel::getBiometricCancellationSignal,
+ onLog = { viewModel.logUiEvent(it) },
)
} else if (credmanBiometricApiEnabled() &&
getCredentialUiState.currentScreenState
@@ -260,12 +261,13 @@
getBiometricPromptState: () -> BiometricPromptState,
onBiometricPromptStateChange: (BiometricPromptState) -> Unit,
getBiometricCancellationSignal: () -> CancellationSignal,
+ onLog: @Composable (UiEventEnum) -> Unit,
) {
if (biometricEntry == null) {
fallbackToOriginalFlow(BiometricFlowType.GET)
return
}
- runBiometricFlowForGet(
+ val biometricFlowCalled = runBiometricFlowForGet(
biometricEntry = biometricEntry,
context = LocalContext.current,
openMoreOptionsPage = onMoreOptionSelected,
@@ -280,6 +282,9 @@
onBiometricFailureFallback = fallbackToOriginalFlow,
getBiometricCancellationSignal = getBiometricCancellationSignal
)
+ if (biometricFlowCalled) {
+ onLog(GetCredentialEvent.CREDMAN_GET_CRED_BIOMETRIC_FLOW_LAUNCHED)
+ }
}
/** Draws the primary credential selection page, used in Android U. */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
index daa42be..dac25fa 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/CreateCredentialEvent.kt
@@ -52,7 +52,10 @@
CREDMAN_CREATE_CRED_EXTERNAL_ONLY_SELECTION(1327),
@UiEvent(doc = "The more about passkeys intro card is visible on screen.")
- CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO(1328);
+ CREDMAN_CREATE_CRED_MORE_ABOUT_PASSKEYS_INTRO(1328),
+
+ @UiEvent(doc = "The single tap biometric flow is launched.")
+ CREDMAN_CREATE_CRED_BIOMETRIC_FLOW_LAUNCHED(1800);
override fun getId(): Int {
return this.id
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt
index 8de8895..8870f28 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/GetCredentialEvent.kt
@@ -54,7 +54,10 @@
CREDMAN_GET_CRED_PRIMARY_SELECTION_CARD(1341),
@UiEvent(doc = "The all sign in option card is visible on screen.")
- CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD(1342);
+ CREDMAN_GET_CRED_ALL_SIGN_IN_OPTION_CARD(1342),
+
+ @UiEvent(doc = "The single tap biometric flow is launched.")
+ CREDMAN_GET_CRED_BIOMETRIC_FLOW_LAUNCHED(1801);
override fun getId(): Int {
return this.id
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
index 59a511d..10e8246 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java
@@ -311,18 +311,18 @@
broadcastIntent,
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);
- try {
- // Delay committing the session by 100ms to fix a UI glitch while displaying the
- // Update-Owner change dialog on top of the Installing dialog
- new Handler(Looper.getMainLooper()).postDelayed(() -> {
+ // Delay committing the session by 100ms to fix a UI glitch while displaying the
+ // Update-Owner change dialog on top of the Installing dialog
+ new Handler(Looper.getMainLooper()).postDelayed(() -> {
+ try {
session.commit(pendingIntent.getIntentSender());
- }, 100);
- } catch (Exception e) {
- Log.e(LOG_TAG, "Cannot install package: ", e);
- launchFailure(PackageInstaller.STATUS_FAILURE,
- PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
- return;
- }
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Cannot install package: ", e);
+ launchFailure(PackageInstaller.STATUS_FAILURE,
+ PackageManager.INSTALL_FAILED_INTERNAL_ERROR, null);
+ return;
+ }
+ }, 100);
mCancelButton.setEnabled(false);
setFinishOnTouchOutside(false);
} else {
diff --git a/packages/SettingsLib/Color/res/values/colors.xml b/packages/SettingsLib/Color/res/values/colors.xml
index ef0dd1b..7097523 100644
--- a/packages/SettingsLib/Color/res/values/colors.xml
+++ b/packages/SettingsLib/Color/res/values/colors.xml
@@ -17,7 +17,6 @@
<resources>
<!-- Dynamic colors-->
- <color name="settingslib_color_blue700">#0B57D0</color>
<color name="settingslib_color_blue600">#1a73e8</color>
<color name="settingslib_color_blue400">#669df6</color>
<color name="settingslib_color_blue300">#8ab4f8</color>
@@ -63,4 +62,5 @@
<color name="settingslib_color_cyan400">#4ecde6</color>
<color name="settingslib_color_cyan300">#78d9ec</color>
<color name="settingslib_color_cyan100">#cbf0f8</color>
+ <color name="settingslib_color_charcoal">#171717</color>
</resources>
diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp
index c3a91a2..cd8f584 100644
--- a/packages/SettingsLib/IllustrationPreference/Android.bp
+++ b/packages/SettingsLib/IllustrationPreference/Android.bp
@@ -47,6 +47,7 @@
aconfig_declarations: "settingslib_illustrationpreference_flags",
min_sdk_version: "30",
+ sdk_version: "system_current",
apex_available: [
"//apex_available:platform",
diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
index bc3488fc..cf645f7 100644
--- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
+++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/LottieColorUtils.java
@@ -41,6 +41,9 @@
static {
HashMap<String, Integer> map = new HashMap<>();
map.put(
+ ".grey200",
+ R.color.settingslib_color_grey800);
+ map.put(
".grey600",
R.color.settingslib_color_grey400);
map.put(
@@ -56,9 +59,6 @@
".black",
android.R.color.white);
map.put(
- ".blue200",
- R.color.settingslib_color_blue700);
- map.put(
".blue400",
R.color.settingslib_color_blue600);
map.put(
@@ -70,6 +70,9 @@
map.put(
".red200",
R.color.settingslib_color_red500);
+ map.put(
+ ".cream",
+ R.color.settingslib_color_charcoal);
DARK_TO_LIGHT_THEME_COLOR_MAP = Collections.unmodifiableMap(map);
}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
index a6cc3a9..ea4480f 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Lottie.kt
@@ -49,6 +49,7 @@
object LottieColorUtils {
private val DARK_TO_LIGHT_THEME_COLOR_MAP = mapOf(
+ ".grey200" to R.color.settingslib_color_grey800,
".grey600" to R.color.settingslib_color_grey400,
".grey800" to R.color.settingslib_color_grey300,
".grey900" to R.color.settingslib_color_grey50,
@@ -58,6 +59,7 @@
".green400" to R.color.settingslib_color_green600,
".green200" to R.color.settingslib_color_green500,
".red200" to R.color.settingslib_color_red500,
+ ".cream" to R.color.settingslib_color_charcoal
)
@Composable
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt
index 71072a5..d91c7e6 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ui/CopyableBodyTest.kt
@@ -16,9 +16,9 @@
package com.android.settingslib.spa.widget.ui
-import android.content.ClipData
-import android.content.ClipboardManager
import android.content.Context
+import androidx.compose.ui.platform.ClipboardManager
+import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.longClick
@@ -63,9 +63,9 @@
@Test
fun onCopy_saveToClipboard() {
- val clipboardManager = context.getSystemService(ClipboardManager::class.java)!!
- clipboardManager.setPrimaryClip(ClipData.newPlainText("", ""))
+ var clipboardManager: ClipboardManager? = null
composeTestRule.setContent {
+ clipboardManager = LocalClipboardManager.current
CopyableBody(TEXT)
}
@@ -74,7 +74,7 @@
}
composeTestRule.onNodeWithText(context.getString(android.R.string.copy)).performClick()
- assertThat(clipboardManager.primaryClip!!.getItemAt(0).text.toString()).isEqualTo(TEXT)
+ assertThat(clipboardManager?.getText()?.text).isEqualTo(TEXT)
}
private companion object {
diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml
index ab04904..470cdee 100644
--- a/packages/SettingsLib/res/values/dimens.xml
+++ b/packages/SettingsLib/res/values/dimens.xml
@@ -32,7 +32,7 @@
<!-- Usage graph dimens -->
<dimen name="usage_graph_margin_top_bottom">9dp</dimen>
- <dimen name="usage_graph_labels_width">56dp</dimen>
+ <dimen name="usage_graph_labels_width">60dp</dimen>
<dimen name="usage_graph_divider_size">1dp</dimen>
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
index 869fb7f..7081195 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/LocalMediaRepository.kt
@@ -81,7 +81,7 @@
localMediaManager.unregisterCallback(callback)
}
}
- .shareIn(coroutineScope, SharingStarted.WhileSubscribed(), replay = 0)
+ .shareIn(coroutineScope, SharingStarted.Eagerly, replay = 0)
override val currentConnectedDevice: StateFlow<MediaDevice?> =
merge(devicesChanges, mediaDevicesUpdates)
@@ -89,8 +89,8 @@
.onStart { emit(localMediaManager.currentConnectedDevice) }
.stateIn(
coroutineScope,
- SharingStarted.WhileSubscribed(),
- localMediaManager.currentConnectedDevice
+ SharingStarted.Eagerly,
+ localMediaManager.currentConnectedDevice,
)
private sealed interface DevicesUpdate {
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index f83928d..03c2a83 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -418,6 +418,7 @@
VALIDATORS.put(Global.Wearable.CHARGING_SOUNDS_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.BEDTIME_MODE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.BEDTIME_HARD_MODE, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Global.Wearable.VIBRATE_FOR_ACTIVE_UNLOCK, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.DYNAMIC_COLOR_THEME_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.SCREENSHOT_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.Wearable.UPGRADE_DATA_MIGRATION_STATUS,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 473955f..4ec170d 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -619,6 +619,7 @@
Settings.Global.Wearable.COOLDOWN_MODE_ON,
Settings.Global.Wearable.BEDTIME_MODE,
Settings.Global.Wearable.BEDTIME_HARD_MODE,
+ Settings.Global.Wearable.VIBRATE_FOR_ACTIVE_UNLOCK,
Settings.Global.Wearable.LOCK_SCREEN_STATE,
Settings.Global.Wearable.DISABLE_AOD_WHILE_PLUGGED,
Settings.Global.Wearable.NETWORK_LOCATION_OPT_IN,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
index ca1e4c1..e4898da 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderMultiUsersTest.java
@@ -27,11 +27,11 @@
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.bedstead.enterprise.annotations.EnsureHasNoWorkProfile;
+import com.android.bedstead.enterprise.annotations.EnsureHasWorkProfile;
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
-import com.android.bedstead.harrier.annotations.EnsureHasNoWorkProfile;
import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
-import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
import com.android.bedstead.harrier.annotations.RequireFeature;
import com.android.bedstead.harrier.annotations.RequireRunOnInitialUser;
import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index e940674..7a098ee 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -78,9 +78,42 @@
visibility: ["//visibility:private"],
}
+// Tests where robolectric conversion caused errors in SystemUITests at runtime
+filegroup {
+ name: "SystemUI-tests-broken-robofiles-sysui-run",
+ srcs: [
+ "tests/src/**/systemui/globalactions/GlobalActionsDialogLiteTest.java",
+ "tests/src/**/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt",
+ "tests/src/**/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt",
+ "tests/src/**/systemui/media/dialog/MediaOutputAdapterTest.java",
+ "tests/src/**/systemui/media/dialog/MediaOutputBaseDialogTest.java",
+ "tests/src/**/systemui/media/dialog/MediaOutputBroadcastDialogTest.java",
+ "tests/src/**/systemui/media/dialog/MediaOutputDialogTest.java",
+ "tests/src/**/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt",
+ ],
+}
+
filegroup {
name: "SystemUI-tests-broken-robofiles-run",
srcs: [
+ "tests/src/**/systemui/keyguard/CustomizationProviderTest.kt",
+ "tests/src/**/systemui/globalactions/GlobalActionsColumnLayoutTest.java",
+ "tests/src/**/systemui/globalactions/GlobalActionsDialogLiteTest.java",
+ "tests/src/**/systemui/globalactions/GlobalActionsImeTest.java",
+ "tests/src/**/systemui/graphics/ImageLoaderTest.kt",
+ "tests/src/**/systemui/keyguard/KeyguardViewMediatorTest.java",
+ "tests/src/**/systemui/keyguard/LifecycleTest.java",
+ "tests/src/**/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt",
+ "tests/src/**/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt",
+ "tests/src/**/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt",
+ "tests/src/**/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt",
+ "tests/src/**/systemui/lifecycle/RepeatWhenAttachedTest.kt",
+ "tests/src/**/systemui/log/LogBufferTest.kt",
+ "tests/src/**/systemui/media/dialog/MediaOutputBaseDialogTest.java",
+ "tests/src/**/systemui/media/dialog/MediaOutputBroadcastDialogTest.java",
+ "tests/src/**/systemui/media/dialog/MediaOutputDialogTest.java",
+ "tests/src/**/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt",
+ "tests/src/**/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt",
"tests/src/**/systemui/util/LifecycleFragmentTest.java",
"tests/src/**/systemui/util/TestableAlertDialogTest.kt",
"tests/src/**/systemui/util/kotlin/PairwiseFlowTest",
@@ -767,6 +800,7 @@
":SystemUI-tests-robofiles",
],
exclude_srcs: [
+ ":SystemUI-tests-broken-robofiles-sysui-run",
":SystemUI-tests-broken-robofiles-compile",
":SystemUI-tests-broken-robofiles-run",
],
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 7ce8f98..3310cfa 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -177,16 +177,9 @@
}
flag {
- name: "notification_throttle_hun"
- namespace: "systemui"
- description: "During notification avalanche, throttle HUNs showing in fast succession."
- bug: "307288824"
-}
-
-flag {
name: "notification_avalanche_throttle_hun"
namespace: "systemui"
- description: "(currently unused) During notification avalanche, throttle HUNs showing in fast succession."
+ description: "During notification avalanche, throttle HUNs showing in fast succession."
bug: "307288824"
}
@@ -743,16 +736,6 @@
}
flag {
- name: "trim_resources_with_background_trim_at_lock"
- namespace: "systemui"
- description: "Trim fonts and other caches when the device locks to lower memory consumption."
- bug: "322143614"
- metadata {
- purpose: PURPOSE_BUGFIX
- }
-}
-
-flag {
name: "dedicated_notif_inflation_thread"
namespace: "systemui"
description: "Create a separate background thread for inflating notifications"
@@ -997,6 +980,13 @@
}
flag {
+ name: "glanceable_hub_fullscreen_swipe"
+ namespace: "systemui"
+ description: "Increase swipe area for gestures to bring in glanceable hub"
+ bug: "339665673"
+}
+
+flag {
name: "glanceable_hub_shortcut_button"
namespace: "systemui"
description: "Shows a button over the dream and lock screen to open the glanceable hub"
@@ -1011,6 +1001,13 @@
}
flag {
+ name: "glanceable_hub_allow_keyguard_when_dreaming"
+ namespace: "systemui"
+ description: "Allows users to exit dream to keyguard with glanceable hub enabled"
+ bug: "343505271"
+}
+
+flag {
name: "new_touchpad_gestures_tutorial"
namespace: "systemui"
description: "Enables new interactive tutorial for learning touchpad gestures"
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt
deleted file mode 100644
index dff8753..0000000
--- a/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt
+++ /dev/null
@@ -1,96 +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.compose.ui.platform
-
-import android.content.Context
-import android.content.res.Configuration
-import android.util.AttributeSet
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.ui.platform.AbstractComposeView
-
-/**
- * A ComposeView that recreates its composition if the display size or font scale was changed.
- *
- * TODO(b/317317814): Remove this workaround.
- */
-class DensityAwareComposeView(context: Context) : OpenComposeView(context) {
- private var lastDensityDpi: Int = -1
- private var lastFontScale: Float = -1f
-
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
-
- val configuration = context.resources.configuration
- lastDensityDpi = configuration.densityDpi
- lastFontScale = configuration.fontScale
- }
-
- override fun dispatchConfigurationChanged(newConfig: Configuration) {
- super.dispatchConfigurationChanged(newConfig)
-
- // If the density or font scale changed, we dispose then recreate the composition. Note that
- // we do this here after dispatching the new configuration to children (instead of doing
- // this in onConfigurationChanged()) because the new configuration should first be
- // dispatched to the AndroidComposeView that holds the current density before we recreate
- // the composition.
- val densityDpi = newConfig.densityDpi
- val fontScale = newConfig.fontScale
- if (densityDpi != lastDensityDpi || fontScale != lastFontScale) {
- lastDensityDpi = densityDpi
- lastFontScale = fontScale
-
- disposeComposition()
- if (isAttachedToWindow) {
- createComposition()
- }
- }
- }
-}
-
-/** A fork of [androidx.compose.ui.platform.ComposeView] that is open and can be subclassed. */
-open class OpenComposeView
-internal constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
- AbstractComposeView(context, attrs, defStyleAttr) {
-
- private val content = mutableStateOf<(@Composable () -> Unit)?>(null)
-
- @Suppress("RedundantVisibilityModifier")
- protected override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
-
- @Composable
- override fun Content() {
- content.value?.invoke()
- }
-
- override fun getAccessibilityClassName(): CharSequence {
- return javaClass.name
- }
-
- /**
- * Set the Jetpack Compose UI content for this view. Initial composition will occur when the
- * view becomes attached to a window or when [createComposition] is called, whichever comes
- * first.
- */
- fun setContent(content: @Composable () -> Unit) {
- shouldCreateCompositionOnAttachedToWindow = true
- this.content.value = content
- if (isAttachedToWindow) {
- createComposition()
- }
- }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
index feb1f5b..a90f82e 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalContainer.kt
@@ -21,6 +21,8 @@
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.compose.animation.scene.CommunalSwipeDetector
+import com.android.compose.animation.scene.DefaultSwipeDetector
import com.android.compose.animation.scene.Edge
import com.android.compose.animation.scene.ElementKey
import com.android.compose.animation.scene.ElementMatcher
@@ -35,6 +37,7 @@
import com.android.compose.animation.scene.observableTransitionState
import com.android.compose.animation.scene.transitions
import com.android.systemui.Flags
+import com.android.systemui.Flags.glanceableHubFullscreenSwipe
import com.android.systemui.communal.shared.model.CommunalScenes
import com.android.systemui.communal.shared.model.CommunalTransitionKeys
import com.android.systemui.communal.ui.compose.extensions.allowGestures
@@ -108,6 +111,8 @@
)
}
+ val detector = remember { CommunalSwipeDetector() }
+
DisposableEffect(state) {
val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope)
dataSourceDelegator.setDelegate(dataSource)
@@ -121,13 +126,25 @@
onDispose { viewModel.setTransitionState(null) }
}
+ val swipeSourceDetector =
+ if (glanceableHubFullscreenSwipe()) {
+ detector
+ } else {
+ FixedSizeEdgeDetector(dimensionResource(id = R.dimen.communal_gesture_initiation_width))
+ }
+
+ val swipeDetector =
+ if (glanceableHubFullscreenSwipe()) {
+ detector
+ } else {
+ DefaultSwipeDetector
+ }
+
SceneTransitionLayout(
state = state,
modifier = modifier.fillMaxSize(),
- swipeSourceDetector =
- FixedSizeEdgeDetector(
- dimensionResource(id = R.dimen.communal_gesture_initiation_width)
- ),
+ swipeSourceDetector = swipeSourceDetector,
+ swipeDetector = swipeDetector,
) {
scene(
CommunalScenes.Blank,
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 9dd3d39..1f7f07b 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
@@ -28,7 +28,9 @@
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@@ -445,6 +447,14 @@
val selected by
remember(index) { derivedStateOf { list[index].key == selectedKey.value } }
DraggableItem(
+ modifier =
+ if (dragDropState.draggingItemIndex == index) {
+ Modifier
+ } else {
+ Modifier.animateItem(
+ placementSpec = spring(stiffness = Spring.StiffnessMediumLow)
+ )
+ },
dragDropState = dragDropState,
selected = selected,
enabled = list[index].isWidgetContent(),
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index c26259f..ee3ffce 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -126,7 +126,8 @@
" size=${coordinates.size}" +
" bounds=$boundsInWindow"
}
- viewModel.onHeadsUpTopChanged(boundsInWindow.top)
+ // Note: boundsInWindow doesn't scroll off the screen
+ stackScrollView.setHeadsUpTop(boundsInWindow.top)
}
) {
content {}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index ae53d56..7ca68fb 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -18,8 +18,8 @@
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.width
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -75,7 +75,7 @@
OverlayShade(
modifier = modifier,
viewModel = overlayShadeViewModel,
- horizontalArrangement = Arrangement.Start,
+ horizontalArrangement = Arrangement.End,
lockscreenContent = lockscreenContent,
) {
Column {
@@ -95,7 +95,7 @@
shouldPunchHoleBehindScrim = false,
shouldFillMaxSize = false,
shadeMode = ShadeMode.Dual,
- modifier = Modifier.width(416.dp),
+ modifier = Modifier.fillMaxWidth(),
)
}
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index 975829a..efda4cd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -17,17 +17,28 @@
package com.android.systemui.scene.ui.composable
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.absoluteOffset
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.IntOffset
import com.android.compose.animation.scene.SceneScope
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.compose.animation.scene.animateSceneFloatAsState
+import com.android.internal.policy.SystemBarUtils
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.notifications.ui.composable.HeadsUpNotificationSpace
import com.android.systemui.qs.ui.composable.QuickSettings
+import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.viewmodel.GoneSceneViewModel
+import com.android.systemui.statusbar.notification.stack.ui.view.NotificationScrollView
+import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
+import dagger.Lazy
import javax.inject.Inject
import kotlinx.coroutines.flow.StateFlow
@@ -39,6 +50,8 @@
class GoneScene
@Inject
constructor(
+ private val notificationStackScrolLView: Lazy<NotificationScrollView>,
+ private val notificationsPlaceholderViewModel: NotificationsPlaceholderViewModel,
private val viewModel: GoneSceneViewModel,
) : ComposableScene {
override val key = Scenes.Gone
@@ -55,5 +68,28 @@
key = QuickSettings.SharedValues.TilesSquishiness,
)
Spacer(modifier.fillMaxSize())
+ HeadsUpNotificationStack(
+ stackScrollView = notificationStackScrolLView.get(),
+ viewModel = notificationsPlaceholderViewModel
+ )
}
}
+
+@Composable
+private fun SceneScope.HeadsUpNotificationStack(
+ stackScrollView: NotificationScrollView,
+ viewModel: NotificationsPlaceholderViewModel,
+) {
+ val context = LocalContext.current
+ val density = LocalDensity.current
+ val statusBarHeight = SystemBarUtils.getStatusBarHeight(context)
+ val headsUpPadding =
+ with(density) { dimensionResource(id = R.dimen.heads_up_status_bar_padding).roundToPx() }
+
+ HeadsUpNotificationSpace(
+ stackScrollView = stackScrollView,
+ viewModel = viewModel,
+ modifier =
+ Modifier.absoluteOffset { IntOffset(x = 0, y = statusBarHeight + headsUpPadding) }
+ )
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
index f5a0ef2..3255b08 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt
@@ -22,6 +22,7 @@
import com.android.systemui.scene.ui.composable.transitions.lockscreenToShadeTransition
import com.android.systemui.scene.ui.composable.transitions.lockscreenToSplitShadeTransition
import com.android.systemui.scene.ui.composable.transitions.shadeToQuickSettingsTransition
+import com.android.systemui.shade.ui.composable.OverlayShade
import com.android.systemui.shade.ui.composable.Shade
/**
@@ -102,4 +103,10 @@
y = { Shade.Dimensions.ScrimOverscrollLimit }
)
}
+ overscroll(Scenes.NotificationsShade, Orientation.Vertical) {
+ translate(OverlayShade.Elements.Panel, y = { OverlayShade.Dimensions.OverscrollLimit })
+ }
+ overscroll(Scenes.QuickSettingsShade, Orientation.Vertical) {
+ translate(OverlayShade.Elements.Panel, y = { OverlayShade.Dimensions.OverscrollLimit })
+ }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
index a6b268d..6b3b760 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToNotificationsShadeTransition.kt
@@ -50,8 +50,7 @@
}
}
- translate(OverlayShade.Elements.PanelBackground, Edge.Top)
- translate(Notifications.Elements.NotificationScrim, Edge.Top)
+ translate(OverlayShade.Elements.Panel, Edge.Top)
fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
index 2baaecf..ec2f14f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/transitions/ToQuickSettingsShadeTransition.kt
@@ -48,7 +48,7 @@
}
}
- translate(OverlayShade.Elements.PanelBackground, Edge.Top)
+ translate(OverlayShade.Elements.Panel, Edge.Top)
fractionRange(end = .5f) { fade(OverlayShade.Elements.Scrim) }
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
index 34cc676..d776e2a 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/OverlayShade.kt
@@ -14,17 +14,28 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalLayoutApi::class)
+
package com.android.systemui.shade.ui.composable
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.calculateEndPadding
+import androidx.compose.foundation.layout.calculateStartPadding
+import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBarsIgnoringVisibility
+import androidx.compose.foundation.layout.waterfall
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
@@ -35,12 +46,12 @@
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
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.modifiers.thenIf
import com.android.compose.windowsizeclass.LocalWindowSizeClass
import com.android.systemui.keyguard.ui.composable.LockscreenContent
import com.android.systemui.scene.shared.model.Scenes
@@ -59,8 +70,6 @@
content: @Composable () -> Unit,
) {
val backgroundScene by viewModel.backgroundScene.collectAsStateWithLifecycle()
- val widthSizeClass = LocalWindowSizeClass.current.widthSizeClass
- val isPanelFullWidth = widthSizeClass == WindowWidthSizeClass.Compact
Box(modifier) {
if (backgroundScene == Scenes.Lockscreen) {
@@ -73,13 +82,13 @@
Scrim(onClicked = viewModel::onScrimClicked)
Row(
- modifier =
- Modifier.fillMaxSize().thenIf(!isPanelFullWidth) {
- Modifier.padding(OverlayShade.Dimensions.ScrimContentPadding)
- },
+ modifier = Modifier.fillMaxSize().panelPadding(),
horizontalArrangement = horizontalArrangement,
) {
- Panel(modifier = Modifier.panelSize(), content = content)
+ Panel(
+ modifier = Modifier.element(OverlayShade.Elements.Panel).panelSize(),
+ content = content
+ )
}
}
}
@@ -135,9 +144,46 @@
)
}
+@Composable
+private fun Modifier.panelPadding(): Modifier {
+ val widthSizeClass = LocalWindowSizeClass.current.widthSizeClass
+ val systemBars = WindowInsets.systemBarsIgnoringVisibility
+ val displayCutout = WindowInsets.displayCutout
+ val waterfall = WindowInsets.waterfall
+ val contentPadding = PaddingValues(all = OverlayShade.Dimensions.ScrimContentPadding)
+
+ val combinedPadding =
+ combinePaddings(
+ systemBars.asPaddingValues(),
+ displayCutout.asPaddingValues(),
+ waterfall.asPaddingValues(),
+ contentPadding
+ )
+
+ return if (widthSizeClass == WindowWidthSizeClass.Compact) {
+ padding(bottom = combinedPadding.calculateBottomPadding())
+ } else {
+ padding(combinedPadding)
+ }
+}
+
+/** Creates a union of [paddingValues] by using the max padding of each edge. */
+@Composable
+private fun combinePaddings(vararg paddingValues: PaddingValues): PaddingValues {
+ val layoutDirection = LocalLayoutDirection.current
+
+ return PaddingValues(
+ start = paddingValues.maxOfOrNull { it.calculateStartPadding(layoutDirection) } ?: 0.dp,
+ top = paddingValues.maxOfOrNull { it.calculateTopPadding() } ?: 0.dp,
+ end = paddingValues.maxOfOrNull { it.calculateEndPadding(layoutDirection) } ?: 0.dp,
+ bottom = paddingValues.maxOfOrNull { it.calculateBottomPadding() } ?: 0.dp
+ )
+}
+
object OverlayShade {
object Elements {
val Scrim = ElementKey("OverlayShadeScrim", scenePicker = LowestZIndexScenePicker)
+ val Panel = ElementKey("OverlayShadePanel", scenePicker = LowestZIndexScenePicker)
val PanelBackground =
ElementKey("OverlayShadePanelBackground", scenePicker = LowestZIndexScenePicker)
}
@@ -153,6 +199,7 @@
val PanelCornerRadius = 46.dp
val PanelWidthMedium = 390.dp
val PanelWidthLarge = 474.dp
+ val OverscrollLimit = 100f
}
object Shapes {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
index da29d58..48af8cd 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/SpatialAudioModule.kt
@@ -16,16 +16,13 @@
package com.android.systemui.volume.panel.component.spatialaudio
-import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
import com.android.systemui.volume.panel.component.shared.model.VolumePanelComponents
import com.android.systemui.volume.panel.component.spatial.domain.SpatialAudioAvailabilityCriteria
-import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
-import com.android.systemui.volume.panel.component.spatialaudio.ui.composable.SpatialAudioPopup
+import com.android.systemui.volume.panel.component.spatialaudio.ui.composable.SpatialAudioComponent
import com.android.systemui.volume.panel.domain.ComponentAvailabilityCriteria
import com.android.systemui.volume.panel.shared.model.VolumePanelUiComponent
import dagger.Binds
import dagger.Module
-import dagger.Provides
import dagger.multibindings.IntoMap
import dagger.multibindings.StringKey
@@ -40,14 +37,8 @@
criteria: SpatialAudioAvailabilityCriteria
): ComponentAvailabilityCriteria
- companion object {
-
- @Provides
- @IntoMap
- @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
- fun provideVolumePanelUiComponent(
- viewModel: SpatialAudioViewModel,
- popup: SpatialAudioPopup,
- ): VolumePanelUiComponent = ButtonComponent(viewModel.spatialAudioButton, popup::show)
- }
+ @Binds
+ @IntoMap
+ @StringKey(VolumePanelComponents.SPATIAL_AUDIO)
+ fun bindVolumePanelUiComponent(component: SpatialAudioComponent): VolumePanelUiComponent
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioComponent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioComponent.kt
new file mode 100644
index 0000000..2d89b5c
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/component/spatialaudio/ui/composable/SpatialAudioComponent.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.volume.panel.component.spatialaudio.ui.composable
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.volume.panel.component.button.ui.composable.ButtonComponent
+import com.android.systemui.volume.panel.component.button.ui.composable.ToggleButtonComponent
+import com.android.systemui.volume.panel.component.spatial.domain.model.SpatialAudioEnabledModel
+import com.android.systemui.volume.panel.component.spatial.ui.viewmodel.SpatialAudioViewModel
+import com.android.systemui.volume.panel.ui.composable.ComposeVolumePanelUiComponent
+import com.android.systemui.volume.panel.ui.composable.VolumePanelComposeScope
+import javax.inject.Inject
+
+/** [ComposeVolumePanelUiComponent] that represents spatial audio button in the Volume Panel. */
+class SpatialAudioComponent
+@Inject
+constructor(
+ private val viewModel: SpatialAudioViewModel,
+ private val popup: SpatialAudioPopup,
+) : ComposeVolumePanelUiComponent {
+
+ @Composable
+ override fun VolumePanelComposeScope.Content(modifier: Modifier) {
+ val shouldUsePopup by viewModel.shouldUsePopup.collectAsStateWithLifecycle()
+
+ val buttonComponent: ComposeVolumePanelUiComponent =
+ remember(shouldUsePopup) {
+ if (shouldUsePopup) {
+ ButtonComponent(viewModel.spatialAudioButton, popup::show)
+ } else {
+ ToggleButtonComponent(viewModel.spatialAudioButton) {
+ if (it) {
+ viewModel.setEnabled(SpatialAudioEnabledModel.SpatialAudioEnabled)
+ } else {
+ viewModel.setEnabled(SpatialAudioEnabledModel.Disabled)
+ }
+ }
+ }
+ }
+ with(buttonComponent) { Content(modifier) }
+ }
+}
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 271eb96..fbf91b7 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
@@ -68,9 +68,7 @@
state.a11yClickDescription?.let {
customActions =
listOf(
- CustomAccessibilityAction(
- it,
- ) {
+ CustomAccessibilityAction(it) {
onIconTapped()
true
}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
index ac5004e..580aba5 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/volume/panel/ui/composable/HorizontalVolumePanelContent.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.panel.ui.composable
+import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -56,17 +57,19 @@
with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) }
}
}
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.spacedBy(spacing),
- ) {
- for (component in layout.footerComponents) {
- AnimatedVisibility(
- visible = component.isVisible,
- modifier = Modifier.weight(1f),
- ) {
- with(component.component as ComposeVolumePanelUiComponent) {
- Content(Modifier)
+ AnimatedContent(
+ targetState = layout.footerComponents,
+ label = "FooterComponentAnimation",
+ ) { footerComponents ->
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(spacing),
+ ) {
+ for (component in footerComponents) {
+ if (component.isVisible) {
+ with(component.component as ComposeVolumePanelUiComponent) {
+ Content(Modifier.weight(1f))
+ }
}
}
}
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 9ea20b9..6349c14 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
@@ -16,6 +16,7 @@
package com.android.systemui.volume.panel.ui.composable
+import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -50,26 +51,27 @@
with(component.component as ComposeVolumePanelUiComponent) { Content(Modifier) }
}
}
- if (layout.footerComponents.isNotEmpty()) {
+
+ AnimatedContent(
+ targetState = layout.footerComponents,
+ label = "FooterComponentAnimation",
+ ) { footerComponents ->
Row(
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 }
+ 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,
- modifier = Modifier.weight(1f),
- ) {
+ for (component in footerComponents) {
+ if (component.isVisible) {
with(component.component as ComposeVolumePanelUiComponent) {
- Content(Modifier)
+ Content(Modifier.weight(1f))
}
}
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.kt
new file mode 100644
index 0000000..7be34ca
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/CommunalSwipeDetector.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.compose.animation.scene
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.positionChange
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+import kotlin.math.abs
+
+private const val TRAVEL_RATIO_THRESHOLD = .5f
+
+/**
+ * {@link CommunalSwipeDetector} provides an implementation of {@link SwipeDetector} and {@link
+ * SwipeSourceDetector} to enable fullscreen swipe handling to transition to and from the glanceable
+ * hub.
+ */
+class CommunalSwipeDetector(private var lastDirection: SwipeSource? = null) :
+ SwipeSourceDetector, SwipeDetector {
+ override fun source(
+ layoutSize: IntSize,
+ position: IntOffset,
+ density: Density,
+ orientation: Orientation
+ ): SwipeSource? {
+ return lastDirection
+ }
+
+ override fun detectSwipe(change: PointerInputChange): Boolean {
+ if (change.positionChange().x > 0) {
+ lastDirection = Edge.Left
+ } else {
+ lastDirection = Edge.Right
+ }
+
+ // Determine whether the ratio of the distance traveled horizontally to the distance
+ // traveled vertically exceeds the threshold.
+ return abs(change.positionChange().x / change.positionChange().y) > TRAVEL_RATIO_THRESHOLD
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index 0fc0053..3cc8431 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -72,6 +72,7 @@
enabled: () -> Boolean,
startDragImmediately: (startedPosition: Offset) -> Boolean,
onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ swipeDetector: SwipeDetector = DefaultSwipeDetector,
): Modifier =
this.then(
MultiPointerDraggableElement(
@@ -79,6 +80,7 @@
enabled,
startDragImmediately,
onDragStarted,
+ swipeDetector,
)
)
@@ -88,6 +90,7 @@
private val startDragImmediately: (startedPosition: Offset) -> Boolean,
private val onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ private val swipeDetector: SwipeDetector,
) : ModifierNodeElement<MultiPointerDraggableNode>() {
override fun create(): MultiPointerDraggableNode =
MultiPointerDraggableNode(
@@ -95,6 +98,7 @@
enabled = enabled,
startDragImmediately = startDragImmediately,
onDragStarted = onDragStarted,
+ swipeDetector = swipeDetector,
)
override fun update(node: MultiPointerDraggableNode) {
@@ -102,6 +106,7 @@
node.enabled = enabled
node.startDragImmediately = startDragImmediately
node.onDragStarted = onDragStarted
+ node.swipeDetector = swipeDetector
}
}
@@ -111,6 +116,7 @@
var startDragImmediately: (startedPosition: Offset) -> Boolean,
var onDragStarted:
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+ var swipeDetector: SwipeDetector = DefaultSwipeDetector,
) :
PointerInputModifierNode,
DelegatingNode(),
@@ -199,6 +205,7 @@
onDragCancel = { controller ->
controller.onStop(velocity = 0f, canChangeScene = true)
},
+ swipeDetector = swipeDetector
)
} catch (exception: CancellationException) {
// If the coroutine scope is active, we can just restart the drag cycle.
@@ -226,7 +233,8 @@
(startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
onDrag: (controller: DragController, change: PointerInputChange, dragAmount: Float) -> Unit,
onDragEnd: (controller: DragController) -> Unit,
- onDragCancel: (controller: DragController) -> Unit
+ onDragCancel: (controller: DragController) -> Unit,
+ swipeDetector: SwipeDetector,
) {
// Wait for a consumable event in [PointerEventPass.Main] pass
val consumablePointer = awaitConsumableEvent().changes.first()
@@ -238,8 +246,10 @@
consumablePointer
} else {
val onSlopReached = { change: PointerInputChange, over: Float ->
- change.consume()
- overSlop = over
+ if (swipeDetector.detectSwipe(change)) {
+ change.consume()
+ overSlop = over
+ }
}
// TODO(b/291055080): Replace by await[Orientation]PointerSlopOrCancellation once it
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 6e29639..2946b04 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -55,6 +55,7 @@
state: SceneTransitionLayoutState,
modifier: Modifier = Modifier,
swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
+ swipeDetector: SwipeDetector = DefaultSwipeDetector,
@FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
scenes: SceneTransitionLayoutScope.() -> Unit,
) {
@@ -62,6 +63,7 @@
state,
modifier,
swipeSourceDetector,
+ swipeDetector,
transitionInterceptionThreshold,
onLayoutImpl = null,
scenes,
@@ -95,6 +97,7 @@
transitions: SceneTransitions,
modifier: Modifier = Modifier,
swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
+ swipeDetector: SwipeDetector = DefaultSwipeDetector,
@FloatRange(from = 0.0, to = 0.5) transitionInterceptionThreshold: Float = 0f,
enableInterruptions: Boolean = DEFAULT_INTERRUPTIONS_ENABLED,
scenes: SceneTransitionLayoutScope.() -> Unit,
@@ -111,6 +114,7 @@
state,
modifier,
swipeSourceDetector,
+ swipeDetector,
transitionInterceptionThreshold,
scenes,
)
@@ -494,6 +498,7 @@
state: SceneTransitionLayoutState,
modifier: Modifier = Modifier,
swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector,
+ swipeDetector: SwipeDetector = DefaultSwipeDetector,
transitionInterceptionThreshold: Float = 0f,
onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null,
scenes: SceneTransitionLayoutScope.() -> Unit,
@@ -529,5 +534,5 @@
layoutImpl.transitionInterceptionThreshold = transitionInterceptionThreshold
}
- layoutImpl.Content(modifier)
+ layoutImpl.Content(modifier, swipeDetector)
}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index f159e27..5fa7c87 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -184,14 +184,14 @@
}
@Composable
- internal fun Content(modifier: Modifier) {
+ internal fun Content(modifier: Modifier, swipeDetector: SwipeDetector) {
Box(
modifier
// Handle horizontal and vertical swipes on this layout.
// Note: order here is important and will give a slight priority to the vertical
// swipes.
- .swipeToScene(horizontalDraggableHandler)
- .swipeToScene(verticalDraggableHandler)
+ .swipeToScene(horizontalDraggableHandler, swipeDetector)
+ .swipeToScene(verticalDraggableHandler, swipeDetector)
.then(LayoutElement(layoutImpl = this))
) {
LookaheadScope {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt
new file mode 100644
index 0000000..54ee783
--- /dev/null
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeDetector.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.input.pointer.PointerInputChange
+
+/** {@link SwipeDetector} helps determine whether a swipe gestured has occurred. */
+@Stable
+interface SwipeDetector {
+ /**
+ * Invoked on changes to pointer input. Returns {@code true} if a swipe has been recognized,
+ * {@code false} otherwise.
+ */
+ fun detectSwipe(change: PointerInputChange): Boolean
+}
+
+val DefaultSwipeDetector = PassthroughSwipeDetector()
+
+/** An {@link SwipeDetector} implementation that recognizes a swipe on any input. */
+class PassthroughSwipeDetector : SwipeDetector {
+ override fun detectSwipe(change: PointerInputChange): Boolean {
+ // Simply accept all changes as a swipe
+ return true
+ }
+}
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index b618369..171e243 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -31,14 +31,18 @@
* Configures the swipeable behavior of a [SceneTransitionLayout] depending on the current state.
*/
@Stable
-internal fun Modifier.swipeToScene(draggableHandler: DraggableHandlerImpl): Modifier {
- return this.then(SwipeToSceneElement(draggableHandler))
+internal fun Modifier.swipeToScene(
+ draggableHandler: DraggableHandlerImpl,
+ swipeDetector: SwipeDetector
+): Modifier {
+ return this.then(SwipeToSceneElement(draggableHandler, swipeDetector))
}
private data class SwipeToSceneElement(
val draggableHandler: DraggableHandlerImpl,
+ val swipeDetector: SwipeDetector
) : ModifierNodeElement<SwipeToSceneNode>() {
- override fun create(): SwipeToSceneNode = SwipeToSceneNode(draggableHandler)
+ override fun create(): SwipeToSceneNode = SwipeToSceneNode(draggableHandler, swipeDetector)
override fun update(node: SwipeToSceneNode) {
node.draggableHandler = draggableHandler
@@ -47,6 +51,7 @@
private class SwipeToSceneNode(
draggableHandler: DraggableHandlerImpl,
+ swipeDetector: SwipeDetector,
) : DelegatingNode(), PointerInputModifierNode {
private val delegate =
delegate(
@@ -55,6 +60,7 @@
enabled = ::enabled,
startDragImmediately = ::startDragImmediately,
onDragStarted = draggableHandler::onDragStarted,
+ swipeDetector = swipeDetector,
)
)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
index aa6d113..4bb643f 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt
@@ -30,6 +30,7 @@
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.input.pointer.AwaitPointerEventScope
import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputChange
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalViewConfiguration
@@ -346,4 +347,69 @@
continueDraggingDown()
assertThat(stopped).isTrue()
}
+
+ @Test
+ fun multiPointerSwipeDetectorInteraction() {
+ val size = 200f
+ val middle = Offset(size / 2f, size / 2f)
+
+ var started = false
+
+ var capturedChange: PointerInputChange? = null
+ var swipeConsume = false
+
+ var touchSlop = 0f
+ rule.setContent {
+ touchSlop = LocalViewConfiguration.current.touchSlop
+ Box(
+ Modifier.size(with(LocalDensity.current) { Size(size, size).toDpSize() })
+ .multiPointerDraggable(
+ orientation = Orientation.Vertical,
+ enabled = { true },
+ startDragImmediately = { false },
+ swipeDetector =
+ object : SwipeDetector {
+ override fun detectSwipe(change: PointerInputChange): Boolean {
+ capturedChange = change
+ return swipeConsume
+ }
+ },
+ onDragStarted = { _, _, _ ->
+ started = true
+ object : DragController {
+ override fun onDrag(delta: Float) {}
+
+ override fun onStop(velocity: Float, canChangeScene: Boolean) {}
+ }
+ },
+ )
+ ) {}
+ }
+
+ fun startDraggingDown() {
+ rule.onRoot().performTouchInput {
+ down(middle)
+ moveBy(Offset(0f, touchSlop))
+ }
+ }
+
+ fun continueDraggingDown() {
+ rule.onRoot().performTouchInput { moveBy(Offset(0f, touchSlop)) }
+ }
+
+ startDraggingDown()
+ assertThat(capturedChange).isNotNull()
+ capturedChange = null
+ assertThat(started).isFalse()
+
+ swipeConsume = true
+ continueDraggingDown()
+ assertThat(capturedChange).isNotNull()
+ capturedChange = null
+
+ continueDraggingDown()
+ assertThat(capturedChange).isNull()
+
+ assertThat(started).isTrue()
+ }
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java b/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java
index 165e972..de9baa5 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java
+++ b/packages/SystemUI/customization/src/com/android/systemui/util/Assert.java
@@ -79,6 +79,21 @@
}
}
+ /**
+ * Asserts that the current thread is the same as the given thread, or that the current thread
+ * is the test thread.
+ * @param expected The looper we expected to be running on
+ */
+ public static void isCurrentThread(Looper expected) {
+ if (!expected.isCurrentThread()
+ && (sTestThread == null || sTestThread != Thread.currentThread())) {
+ throw new IllegalStateException("Called on wrong thread thread."
+ + " wanted " + expected.getThread().getName()
+ + " but instead got Thread.currentThread()="
+ + Thread.currentThread().getName());
+ }
+ }
+
public static void isNotMainThread() {
if (sMainLooper.isCurrentThread()
&& (sTestThread == null || sTestThread == Thread.currentThread())) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
index e39ad4f..a676c7d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/data/repository/ScreenBrightnessDisplayManagerRepositoryTest.kt
@@ -25,15 +25,18 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.brightness.shared.model.LinearBrightness
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.core.FakeLogBuffer
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -74,6 +77,8 @@
ScreenBrightnessDisplayManagerRepository(
displayId,
displayManager,
+ FakeLogBuffer.Factory.create(),
+ mock<TableLogBuffer>(),
kosmos.applicationCoroutineScope,
kosmos.testDispatcher,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt
index 33c44f8..b6616bf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorTest.kt
@@ -20,13 +20,16 @@
import androidx.test.filters.SmallTest
import com.android.settingslib.display.BrightnessUtils
import com.android.systemui.SysuiTestCase
-import com.android.systemui.brightness.data.model.LinearBrightness
import com.android.systemui.brightness.data.repository.fakeScreenBrightnessRepository
import com.android.systemui.brightness.data.repository.screenBrightnessRepository
-import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.brightness.shared.model.GammaBrightness
+import com.android.systemui.brightness.shared.model.LinearBrightness
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.mock
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -41,7 +44,14 @@
private val kosmos = testKosmos()
- private val underTest = ScreenBrightnessInteractor(kosmos.screenBrightnessRepository)
+ private val underTest =
+ with(kosmos) {
+ ScreenBrightnessInteractor(
+ screenBrightnessRepository,
+ applicationCoroutineScope,
+ mock<TableLogBuffer>()
+ )
+ }
@Test
fun gammaBrightness() =
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
index 0058ee4..8402676 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelTest.kt
@@ -20,15 +20,16 @@
import androidx.test.filters.SmallTest
import com.android.settingslib.display.BrightnessUtils
import com.android.systemui.SysuiTestCase
-import com.android.systemui.brightness.data.model.LinearBrightness
import com.android.systemui.brightness.data.repository.fakeScreenBrightnessRepository
import com.android.systemui.brightness.domain.interactor.brightnessPolicyEnforcementInteractor
import com.android.systemui.brightness.domain.interactor.screenBrightnessInteractor
-import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.brightness.shared.model.GammaBrightness
+import com.android.systemui.brightness.shared.model.LinearBrightness
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testScope
import com.android.systemui.res.R
import com.android.systemui.testKosmos
@@ -52,6 +53,7 @@
BrightnessSliderViewModel(
screenBrightnessInteractor,
brightnessPolicyEnforcementInteractor,
+ applicationCoroutineScope,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 5e19a41..a1bad39 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -54,6 +54,8 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.DozeStateModel
+import com.android.systemui.keyguard.shared.model.DozeTransitionModel
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
@@ -62,6 +64,8 @@
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
import com.android.systemui.media.controls.ui.view.MediaHost
+import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest
+import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.settings.fakeUserTracker
import com.android.systemui.shade.ShadeTestUtil
import com.android.systemui.shade.domain.interactor.shadeInteractor
@@ -71,7 +75,6 @@
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.user.data.repository.fakeUserRepository
-import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
@@ -85,6 +88,7 @@
import org.mockito.Mockito
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.whenever
import platform.test.runner.parameterized.ParameterizedAndroidJunit4
import platform.test.runner.parameterized.Parameters
@@ -138,6 +142,8 @@
)
whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id))
+ kosmos.powerInteractor.setAwakeForTest()
+
underTest =
CommunalViewModel(
testScope,
@@ -468,6 +474,229 @@
assertThat(isFocusable).isEqualTo(false)
}
+ @Test
+ fun isCommunalContentFlowFrozen_whenActivityStartedWhileDreaming() =
+ testScope.runTest {
+ val isCommunalContentFlowFrozen by
+ collectLastValue(underTest.isCommunalContentFlowFrozen)
+
+ // 1. When dreaming not dozing
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDreamingWithOverlay(true)
+ advanceTimeBy(60L)
+ // And keyguard is occluded by dream
+ keyguardRepository.setKeyguardOccluded(true)
+
+ // And on hub
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+
+ // Then flow is not frozen
+ assertThat(isCommunalContentFlowFrozen).isEqualTo(false)
+
+ // 2. When dreaming stopped by the new activity about to show on lock screen
+ keyguardRepository.setDreamingWithOverlay(false)
+ advanceTimeBy(60L)
+
+ // Then flow is frozen
+ assertThat(isCommunalContentFlowFrozen).isEqualTo(true)
+
+ // 3. When transitioned to OCCLUDED and activity shows
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ testScope = testScope,
+ )
+
+ // Then flow is not frozen
+ assertThat(isCommunalContentFlowFrozen).isEqualTo(false)
+ }
+
+ @Test
+ fun isCommunalContentFlowFrozen_whenActivityStartedInHandheldMode() =
+ testScope.runTest {
+ val isCommunalContentFlowFrozen by
+ collectLastValue(underTest.isCommunalContentFlowFrozen)
+
+ // 1. When on keyguard and not occluded
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setKeyguardOccluded(false)
+
+ // And transitioned to hub
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+
+ // Then flow is not frozen
+ assertThat(isCommunalContentFlowFrozen).isEqualTo(false)
+
+ // 2. When occluded by a new activity
+ keyguardRepository.setKeyguardOccluded(true)
+ runCurrent()
+
+ // And transitioning to occluded
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ transitionState = TransitionState.STARTED,
+ )
+ )
+
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ transitionState = TransitionState.RUNNING,
+ value = 0.5f,
+ )
+
+ // Then flow is frozen
+ assertThat(isCommunalContentFlowFrozen).isEqualTo(true)
+
+ // 3. When transition is finished
+ keyguardTransitionRepository.sendTransitionStep(
+ from = KeyguardState.GLANCEABLE_HUB,
+ to = KeyguardState.OCCLUDED,
+ transitionState = TransitionState.FINISHED,
+ value = 1f,
+ )
+
+ // Then flow is not frozen
+ assertThat(isCommunalContentFlowFrozen).isEqualTo(false)
+ }
+
+ @Test
+ fun communalContent_emitsFrozenContent_whenFrozen() =
+ testScope.runTest {
+ val communalContent by collectLastValue(underTest.communalContent)
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ // When dreaming
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDreamingWithOverlay(true)
+ advanceTimeBy(60L)
+ keyguardRepository.setKeyguardOccluded(true)
+
+ // And transitioned to hub
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+
+ // Widgets available
+ val widgets =
+ listOf(
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 0,
+ priority = 30,
+ providerInfo = providerInfo,
+ ),
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 1,
+ priority = 20,
+ providerInfo = providerInfo,
+ ),
+ )
+ widgetRepository.setCommunalWidgets(widgets)
+
+ // Then hub shows widgets and the CTA tile
+ assertThat(communalContent).hasSize(3)
+
+ // When dreaming stopped by another activity which should freeze flow
+ keyguardRepository.setDreamingWithOverlay(false)
+ advanceTimeBy(60L)
+
+ // New timer available
+ val target = Mockito.mock(SmartspaceTarget::class.java)
+ whenever<String?>(target.smartspaceTargetId).thenReturn("target")
+ whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+ whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
+ smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
+ runCurrent()
+
+ // Still only emits widgets and the CTA tile
+ assertThat(communalContent).hasSize(3)
+ assertThat(communalContent?.get(0))
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
+ assertThat(communalContent?.get(1))
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
+ assertThat(communalContent?.get(2))
+ .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
+ }
+
+ @Test
+ fun communalContent_emitsLatestContent_whenNotFrozen() =
+ testScope.runTest {
+ val communalContent by collectLastValue(underTest.communalContent)
+ tutorialRepository.setTutorialSettingState(Settings.Secure.HUB_MODE_TUTORIAL_COMPLETED)
+
+ // When dreaming
+ keyguardRepository.setDozeTransitionModel(
+ DozeTransitionModel(from = DozeStateModel.DOZE, to = DozeStateModel.FINISH)
+ )
+ keyguardRepository.setDreaming(true)
+ keyguardRepository.setDreamingWithOverlay(true)
+ advanceTimeBy(60L)
+ keyguardRepository.setKeyguardOccluded(true)
+
+ // Transitioned to Glanceable hub.
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+
+ // And widgets available
+ val widgets =
+ listOf(
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 0,
+ priority = 30,
+ providerInfo = providerInfo,
+ ),
+ CommunalWidgetContentModel.Available(
+ appWidgetId = 1,
+ priority = 20,
+ providerInfo = providerInfo,
+ ),
+ )
+ widgetRepository.setCommunalWidgets(widgets)
+
+ // Then emits widgets and the CTA tile
+ assertThat(communalContent).hasSize(3)
+
+ // When new timer available
+ val target = Mockito.mock(SmartspaceTarget::class.java)
+ whenever(target.smartspaceTargetId).thenReturn("target")
+ whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+ whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
+ smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
+ runCurrent()
+
+ // Then emits timer, widgets and the CTA tile
+ assertThat(communalContent).hasSize(4)
+ assertThat(communalContent?.get(0))
+ .isInstanceOf(CommunalContentModel.Smartspace::class.java)
+ assertThat(communalContent?.get(1))
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
+ assertThat(communalContent?.get(2))
+ .isInstanceOf(CommunalContentModel.WidgetContent::class.java)
+ assertThat(communalContent?.get(3))
+ .isInstanceOf(CommunalContentModel.CtaTileInViewMode::class.java)
+ }
+
private suspend fun setIsMainUser(isMainUser: Boolean) {
whenever(user.isMain).thenReturn(isMainUser)
userRepository.setUserInfos(listOf(user))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
index 723f6a2..9300db9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamServiceTest.kt
@@ -16,29 +16,40 @@
package com.android.systemui.dreams.homecontrols
import android.app.Activity
+import android.content.Intent
+import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_ACTIVITY_PANEL
+import android.service.controls.ControlsProviderService.CONTROLS_SURFACE_DREAM
+import android.service.controls.ControlsProviderService.EXTRA_CONTROLS_SURFACE
+import android.window.TaskFragmentInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
-import com.android.systemui.log.core.FakeLogBuffer.Factory.Companion.create
import com.android.systemui.log.logcatLogBuffer
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
-import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.wakelock.WakeLockFake
import com.google.common.truth.Truth.assertThat
import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.any
+import org.mockito.kotlin.argThat
+import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(AndroidJUnit4::class)
class HomeControlsDreamServiceTest : SysuiTestCase() {
@@ -46,31 +57,38 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder
- private lateinit var fakeWakeLock: WakeLockFake
+ private val fakeWakeLock = WakeLockFake()
+ private val fakeWakeLockBuilder by lazy {
+ WakeLockFake.Builder(context).apply { setWakeLock(fakeWakeLock) }
+ }
- @Mock private lateinit var taskFragmentComponentFactory: TaskFragmentComponent.Factory
- @Mock private lateinit var taskFragmentComponent: TaskFragmentComponent
- @Mock private lateinit var activity: Activity
+ private val taskFragmentComponent = mock<TaskFragmentComponent>()
+ private val activity = mock<Activity>()
+ private val onCreateCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>()
+ private val onInfoChangedCallback = argumentCaptor<(TaskFragmentInfo) -> Unit>()
+ private val hideCallback = argumentCaptor<() -> Unit>()
+ private val dreamServiceDelegate =
+ mock<DreamServiceDelegate> { on { getActivity(any()) } doReturn activity }
- private lateinit var underTest: HomeControlsDreamService
+ private val taskFragmentComponentFactory =
+ mock<TaskFragmentComponent.Factory> {
+ on {
+ create(
+ activity = eq(activity),
+ onCreateCallback = onCreateCallback.capture(),
+ onInfoChangedCallback = onInfoChangedCallback.capture(),
+ hide = hideCallback.capture(),
+ )
+ } doReturn taskFragmentComponent
+ }
+
+ private val underTest: HomeControlsDreamService by lazy { buildService() }
@Before
- fun setup() =
- with(kosmos) {
- MockitoAnnotations.initMocks(this@HomeControlsDreamServiceTest)
- whenever(taskFragmentComponentFactory.create(any(), any(), any(), any()))
- .thenReturn(taskFragmentComponent)
-
- fakeWakeLock = WakeLockFake()
- fakeWakeLockBuilder = WakeLockFake.Builder(context)
- fakeWakeLockBuilder.setWakeLock(fakeWakeLock)
-
- whenever(controlsComponent.getControlsListingController())
- .thenReturn(Optional.of(controlsListingController))
-
- underTest = buildService { activity }
- }
+ fun setup() {
+ whenever(kosmos.controlsComponent.getControlsListingController())
+ .thenReturn(Optional.of(kosmos.controlsListingController))
+ }
@Test
fun testOnAttachedToWindowCreatesTaskFragmentComponent() =
@@ -90,9 +108,12 @@
@Test
fun testNotCreatingTaskFragmentComponentWhenActivityIsNull() =
testScope.runTest {
- underTest = buildService { null }
+ val serviceWithNullActivity =
+ buildService(
+ mock<DreamServiceDelegate> { on { getActivity(underTest) } doReturn null }
+ )
- underTest.onAttachedToWindow()
+ serviceWithNullActivity.onAttachedToWindow()
verify(taskFragmentComponentFactory, never()).create(any(), any(), any(), any())
}
@@ -102,6 +123,7 @@
underTest.onAttachedToWindow()
assertThat(fakeWakeLock.isHeld).isTrue()
}
+
@Test
fun testDetachWindow_wakeLockCanBeReleased() =
testScope.runTest {
@@ -112,14 +134,60 @@
assertThat(fakeWakeLock.isHeld).isFalse()
}
- private fun buildService(activityProvider: DreamActivityProvider): HomeControlsDreamService =
+ @Test
+ fun testFinishesDreamWithoutRestartingActivityWhenNotRedirectingWakes() =
+ testScope.runTest {
+ whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(false)
+ underTest.onAttachedToWindow()
+ onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>())
+ verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())
+
+ // Task fragment becomes empty
+ onInfoChangedCallback.firstValue.invoke(
+ mock<TaskFragmentInfo> { on { isEmpty } doReturn true }
+ )
+ advanceUntilIdle()
+ // Dream is finished and activity is not restarted
+ verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())
+ verify(dreamServiceDelegate, never()).wakeUp(any())
+ verify(dreamServiceDelegate).finish(any())
+ }
+
+ @Test
+ fun testRestartsActivityWhenRedirectingWakes() =
+ testScope.runTest {
+ whenever(dreamServiceDelegate.redirectWake(any())).thenReturn(true)
+ underTest.onAttachedToWindow()
+ onCreateCallback.firstValue.invoke(mock<TaskFragmentInfo>())
+ verify(taskFragmentComponent, times(1)).startActivityInTaskFragment(intentMatcher())
+
+ // Task fragment becomes empty
+ onInfoChangedCallback.firstValue.invoke(
+ mock<TaskFragmentInfo> { on { isEmpty } doReturn true }
+ )
+ advanceUntilIdle()
+ // Activity is restarted instead of finishing the dream.
+ verify(taskFragmentComponent, times(2)).startActivityInTaskFragment(intentMatcher())
+ verify(dreamServiceDelegate).wakeUp(any())
+ verify(dreamServiceDelegate, never()).finish(any())
+ }
+
+ private fun intentMatcher() =
+ argThat<Intent> {
+ getIntExtra(EXTRA_CONTROLS_SURFACE, CONTROLS_SURFACE_ACTIVITY_PANEL) ==
+ CONTROLS_SURFACE_DREAM
+ }
+
+ private fun buildService(
+ activityProvider: DreamServiceDelegate = dreamServiceDelegate
+ ): HomeControlsDreamService =
with(kosmos) {
return HomeControlsDreamService(
controlsSettingsRepository = FakeControlsSettingsRepository(),
taskFragmentFactory = taskFragmentComponentFactory,
homeControlsComponentInteractor = homeControlsComponentInteractor,
- fakeWakeLockBuilder,
- dreamActivityProvider = activityProvider,
+ wakeLockBuilder = fakeWakeLockBuilder,
+ dreamServiceDelegate = activityProvider,
bgDispatcher = testDispatcher,
logBuffer = logcatLogBuffer("HomeControlsDreamServiceTest")
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
index c51413a..4849e66 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/haptics/qs/QSLongPressEffectTest.kt
@@ -26,6 +26,7 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.qsTileFactory
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.TestScope
@@ -41,6 +42,7 @@
private val kosmos = testKosmos()
private val vibratorHelper = kosmos.vibratorHelper
+ private val qsTile = kosmos.qsTileFactory.createTile("Test Tile")
private val effectDuration = 400
private val lowTickDuration = 12
@@ -61,6 +63,7 @@
vibratorHelper,
kosmos.keyguardInteractor,
)
+ longPressEffect.qsTile = qsTile
}
@Test
@@ -91,8 +94,10 @@
// GIVEN an action down event occurs
longPressEffect.handleActionDown()
- // THEN the effect moves to the TIMEOUT_WAIT state
+ // THEN the effect moves to the TIMEOUT_WAIT state and starts the wait
+ val action by collectLastValue(longPressEffect.actionType)
assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ assertThat(action).isEqualTo(QSLongPressEffect.ActionType.WAIT_TAP_TIMEOUT)
}
@Test
@@ -107,20 +112,6 @@
}
@Test
- fun onActionUp_whileWaiting_performsClick() =
- testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
- // GIVEN an action is being collected
- val action by collectLastValue(longPressEffect.actionType)
-
- // GIVEN an action up occurs
- longPressEffect.handleActionUp()
-
- // THEN the action to invoke is the click action and the effect does not start
- assertThat(action).isEqualTo(QSLongPressEffect.ActionType.CLICK)
- assertEffectDidNotStart()
- }
-
- @Test
fun onWaitComplete_whileWaiting_beginsEffect() =
testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
// GIVEN the pressed timeout is complete
@@ -221,8 +212,10 @@
// GIVEN that the animator was cancelled
longPressEffect.handleAnimationCancel()
- // THEN the state goes to the timeout wait
+ // THEN the state goes to the timeout wait and the wait is posted
+ val action by collectLastValue(longPressEffect.actionType)
assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.TIMEOUT_WAIT)
+ assertThat(action).isEqualTo(QSLongPressEffect.ActionType.WAIT_TAP_TIMEOUT)
}
@Test
@@ -238,6 +231,29 @@
assertThat(longPressEffect.state).isEqualTo(QSLongPressEffect.State.IDLE)
}
+ @Test
+ fun onTileClick_whileWaiting_withQSTile_clicks() =
+ testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
+ // GIVEN that a click was detected
+ val couldClick = longPressEffect.onTileClick()
+
+ // THEN the click is successful
+ assertThat(couldClick).isTrue()
+ }
+
+ @Test
+ fun onTileClick_whileWaiting_withoutQSTile_cannotClick() =
+ testWhileInState(QSLongPressEffect.State.TIMEOUT_WAIT) {
+ // GIVEN that no QSTile has been set
+ longPressEffect.qsTile = null
+
+ // GIVEN that a click was detected
+ val couldClick = longPressEffect.onTileClick()
+
+ // THEN the click is not successful
+ assertThat(couldClick).isFalse()
+ }
+
private fun testWithScope(initialize: Boolean = true, test: suspend TestScope.() -> Unit) =
with(kosmos) {
testScope.runTest {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
index d630a2f..6c5001a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractorTest.kt
@@ -136,20 +136,20 @@
@Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
- fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissableKeyguard() =
+ fun testTransitionToOccluded_onWakeUp_ifPowerButtonGestureDetected_fromAod_nonDismissibleKeyguard() =
testScope.runTest {
powerInteractor.onCameraLaunchGestureDetected()
powerInteractor.setAwakeForTest()
advanceTimeBy(100) // account for debouncing
- // We should head back to GONE since we started there.
+ // We should head to OCCLUDED because keyguard is not dismissible.
assertThat(transitionRepository)
.startedTransition(from = KeyguardState.AOD, to = KeyguardState.OCCLUDED)
}
@Test
@EnableFlags(Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR)
- fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissableKeyguard() =
+ fun testTransitionToGone_onWakeUp_ifPowerButtonGestureDetected_fromAod_dismissibleKeyguard() =
testScope.runTest {
kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
powerInteractor.onCameraLaunchGestureDetected()
@@ -188,6 +188,7 @@
)
// Detect a power gesture and then wake up.
+ kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
reset(transitionRepository)
powerInteractor.onCameraLaunchGestureDetected()
powerInteractor.setAwakeForTest()
@@ -355,6 +356,7 @@
)
// Detect a power gesture and then wake up.
+ kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
reset(transitionRepository)
powerInteractor.onCameraLaunchGestureDetected()
powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
index bfc7775..612f2e7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractorTest.kt
@@ -56,13 +56,13 @@
import com.android.systemui.power.domain.interactor.powerInteractor
import com.android.systemui.testKosmos
import junit.framework.Assert.assertEquals
-import kotlin.test.Test
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
@@ -230,6 +230,7 @@
)
// Detect a power gesture and then wake up.
+ kosmos.fakeKeyguardRepository.setKeyguardDismissible(true)
reset(transitionRepository)
powerInteractor.onCameraLaunchGestureDetected()
powerInteractor.setAwakeForTest()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 78a1167..5068f68 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -350,7 +350,7 @@
}
@Test
- fun quickAffordance_updateOncePerShadeExpansion() =
+ fun quickAffordance_doNotSendUpdatesWhileShadeExpandingAndStillHidden() =
testScope.runTest {
val shadeExpansion = MutableStateFlow(0f)
whenever(shadeInteractor.anyExpansion).thenReturn(shadeExpansion)
@@ -365,7 +365,9 @@
shadeExpansion.value = i / 10f
}
- assertThat(collectedValue.size).isEqualTo(initialSize + 1)
+ assertThat(collectedValue[0])
+ .isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java)
+ assertThat(collectedValue.size).isEqualTo(initialSize)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
index 04c270d..ad24a71 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt
@@ -28,6 +28,7 @@
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.kosmos.testScope
@@ -104,6 +105,7 @@
burnInInteractor = burnInInteractor,
shortcutsCombinedViewModel = shortcutsCombinedViewModel,
configurationInteractor = ConfigurationInteractor(FakeConfigurationRepository()),
+ keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 37d4721..7ebebd7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -203,6 +203,21 @@
.containsExactlyElementsIn(DEFAULT_TILES.toTileSpecs() + startingTiles)
}
+ @Test
+ fun prependDefault_noChangesWhenInRetail() =
+ testScope.runTest {
+ val user = 0
+ retailModeRepository.setRetailMode(true)
+ val startingTiles = "a"
+ storeTilesForUser(startingTiles, user)
+
+ runCurrent()
+ underTest.prependDefault(user)
+ runCurrent()
+
+ assertThat(loadTilesForUser(user)).isEqualTo(startingTiles)
+ }
+
private fun TestScope.storeTilesForUser(specs: String, forUser: Int) {
secureSettings.putStringForUser(SETTING, specs, forUser)
runCurrent()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
index 58fc109..b12fbc2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepositoryTest.kt
@@ -327,6 +327,32 @@
assertThat(loadTiles()).isEqualTo(expected)
}
+ @Test
+ fun setTilesWithRepeats_onlyDistinctTiles() =
+ testScope.runTest {
+ val tilesToSet = "a,b,c,a,d,b".toTileSpecs()
+ val expected = "a,b,c,d"
+
+ val tiles by collectLastValue(underTest.tiles())
+ underTest.setTiles(tilesToSet)
+
+ assertThat(tiles).isEqualTo(expected.toTileSpecs())
+ assertThat(loadTiles()).isEqualTo(expected)
+ }
+
+ @Test
+ fun prependDefaultTwice_doesntAddMoreTiles() =
+ testScope.runTest {
+ val tiles by collectLastValue(underTest.tiles())
+ underTest.setTiles(listOf(TileSpec.create("a")))
+
+ underTest.prependDefault()
+ val currentTiles = tiles!!
+ underTest.prependDefault()
+
+ assertThat(tiles).isEqualTo(currentTiles)
+ }
+
private fun getDefaultTileSpecs(): List<TileSpec> {
return defaultTilesRepository.defaultTiles
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 1c73fe2b..6ad4b31 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -49,6 +49,7 @@
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.qs.tiles.di.NewQSTileFactory
import com.android.systemui.qs.toProto
+import com.android.systemui.retail.data.repository.FakeRetailModeRepository
import com.android.systemui.settings.UserTracker
import com.android.systemui.user.data.repository.FakeUserRepository
import com.android.systemui.util.mockito.any
@@ -85,6 +86,7 @@
private val pipelineFlags = QSPipelineFlagsRepository()
private val tileLifecycleManagerFactory = TLMFactory()
private val minimumTilesRepository = MinimumTilesFixedRepository()
+ private val retailModeRepository = FakeRetailModeRepository()
@Mock private lateinit var customTileStatePersister: CustomTileStatePersister
@@ -118,6 +120,7 @@
installedTilesComponentRepository = installedTilesPackageRepository,
userRepository = userRepository,
minimumTilesRepository = minimumTilesRepository,
+ retailModeRepository = retailModeRepository,
customTileStatePersister = customTileStatePersister,
tileFactory = tileFactory,
newQSTileFactory = { newQSTileFactory },
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
index 260189d..e8ad038 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
@@ -34,6 +34,8 @@
import com.android.systemui.qs.pipeline.data.repository.fakeDefaultTilesRepository
import com.android.systemui.qs.pipeline.data.repository.fakeMinimumTilesRepository
import com.android.systemui.qs.pipeline.data.repository.fakeRestoreRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeRetailModeRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeTileSpecRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.qsTileFactory
import com.android.systemui.settings.fakeUserTracker
@@ -138,6 +140,19 @@
}
}
+ @Test
+ fun inRetailMode_onlyOneTile_noPrependDefault() =
+ with(kosmos) {
+ testScope.runTest {
+ fakeRetailModeRepository.setRetailMode(true)
+ fakeTileSpecRepository.setTiles(0, listOf(goodTile))
+ val tiles by collectLastValue(currentTilesInteractor.currentTiles)
+ runCurrent()
+
+ assertThat(tiles!!.map { it.spec }).isEqualTo(listOf(goodTile))
+ }
+ }
+
private fun tileCreator(spec: String): QSTile? {
return if (spec.contains("OEM")) {
null // We don't know how to create OEM spec tiles
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 02993b8..523a89a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -99,14 +99,14 @@
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
-import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
-import platform.test.runner.parameterized.Parameters;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
@RunWith(ParameterizedAndroidJunit4.class)
public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
@@ -162,6 +162,7 @@
private NotificationEntry mCurrentUserNotif;
private NotificationEntry mSecondaryUserNotif;
private NotificationEntry mWorkProfileNotif;
+ private NotificationEntry mSensitiveContentNotif;
private final FakeFeatureFlagsClassic mFakeFeatureFlags = new FakeFeatureFlagsClassic();
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
private final FakeExecutor mBackgroundExecutor = new FakeExecutor(mFakeSystemClock);
@@ -224,6 +225,14 @@
mWorkProfileNotif.setRanking(new RankingBuilder(mWorkProfileNotif.getRanking())
.setChannel(channel)
.setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
+ mSensitiveContentNotif = new NotificationEntryBuilder()
+ .setNotification(notifWithPrivateVisibility)
+ .setUser(new UserHandle(mCurrentUser.id))
+ .build();
+ mSensitiveContentNotif.setRanking(new RankingBuilder(mCurrentUserNotif.getRanking())
+ .setChannel(channel)
+ .setSensitiveContent(true)
+ .setVisibilityOverride(VISIBILITY_NO_OVERRIDE).build());
when(mNotifCollection.getEntry(mWorkProfileNotif.getKey())).thenReturn(mWorkProfileNotif);
mLockscreenUserManager = new TestNotificationLockscreenUserManager(mContext);
@@ -459,6 +468,17 @@
}
@Test
+ public void testHasSensitiveContent_redacted() {
+ // Allow private notifications for this user
+ mSettings.putIntForUser(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, 0,
+ mCurrentUser.id);
+ changeSetting(LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS);
+
+ // Sensitive Content notifications are always redacted
+ assertTrue(mLockscreenUserManager.needsRedaction(mSensitiveContentNotif));
+ }
+
+ @Test
public void testUserSwitchedCallsOnUserSwitching() {
mLockscreenUserManager.getUserTrackerCallbackForTest().onUserChanging(mSecondaryUser.id,
mContext);
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 82e2bb7..c35c165 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
@@ -41,6 +41,8 @@
import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.StatusBarState
import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
@@ -53,6 +55,7 @@
import com.android.systemui.res.R
import com.android.systemui.shade.mockLargeScreenHeaderHelper
import com.android.systemui.shade.shadeTestUtil
+import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
@@ -794,11 +797,47 @@
@DisableSceneContainer
fun updateBounds_fromKeyguardRoot() =
testScope.runTest {
- val bounds by collectLastValue(underTest.bounds)
+ val startProgress = 0f
+ val startStep = TransitionStep(LOCKSCREEN, AOD, startProgress, TransitionState.STARTED)
+ val boundsChangingProgress = 0.2f
+ val boundsChangingStep =
+ TransitionStep(LOCKSCREEN, AOD, boundsChangingProgress, TransitionState.RUNNING)
+ val boundsInterpolatingProgress = 0.6f
+ val boundsInterpolatingStep =
+ TransitionStep(
+ LOCKSCREEN,
+ AOD,
+ boundsInterpolatingProgress,
+ TransitionState.RUNNING
+ )
+ val finishProgress = 1.0f
+ val finishStep =
+ TransitionStep(LOCKSCREEN, AOD, finishProgress, TransitionState.FINISHED)
+ val bounds by collectLastValue(underTest.bounds)
val top = 123f
val bottom = 456f
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(startStep)
+ runCurrent()
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsChangingStep)
+ runCurrent()
keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom)
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(boundsInterpolatingStep)
+ runCurrent()
+ val adjustedProgress =
+ (boundsInterpolatingProgress - boundsChangingProgress) /
+ (1 - boundsChangingProgress)
+ val interpolatedTop = interpolate(0f, top, adjustedProgress)
+ val interpolatedBottom = interpolate(0f, bottom, adjustedProgress)
+ assertThat(bounds)
+ .isEqualTo(
+ NotificationContainerBounds(top = interpolatedTop, bottom = interpolatedBottom)
+ )
+
+ kosmos.fakeKeyguardTransitionRepository.sendTransitionStep(finishStep)
+ runCurrent()
assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom))
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
index 63f19fb..6b5d072 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt
@@ -78,7 +78,7 @@
// Initialize AvalancheController and TestableHeadsUpManager during setUp instead of
// declaration, where mocks are null
- mAvalancheController = AvalancheController(dumpManager)
+ mAvalancheController = AvalancheController(dumpManager, mUiEventLoggerFake)
testableHeadsUpManager =
TestableHeadsUpManager(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 3bfc046..88bef91 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -38,6 +38,7 @@
import android.app.Notification;
import android.app.PendingIntent;
import android.app.Person;
+import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.FlagsParameterization;
import android.testing.TestableLooper;
@@ -147,7 +148,7 @@
@Override
public void SysuiSetup() throws Exception {
super.SysuiSetup();
- mAvalancheController = new AvalancheController(dumpManager);
+ mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake);
}
@Test
@@ -610,7 +611,31 @@
}
@Test
- public void testPinEntry_logsPeek() {
+ @EnableFlags(NotificationThrottleHun.FLAG_NAME)
+ public void testPinEntry_logsPeek_throttleEnabled() {
+ final BaseHeadsUpManager hum = createHeadsUpManager();
+
+ // Needs full screen intent in order to be pinned
+ final BaseHeadsUpManager.HeadsUpEntry entryToPin = hum.new HeadsUpEntry(
+ HeadsUpManagerTestUtil.createFullScreenIntentEntry(/* id = */ 0, mContext));
+
+ // Note: the standard way to show a notification would be calling showNotification rather
+ // than onAlertEntryAdded. However, in practice showNotification in effect adds
+ // the notification and then updates it; in order to not log twice, the entry needs
+ // to have a functional ExpandableNotificationRow that can keep track of whether it's
+ // pinned or not (via isRowPinned()). That feels like a lot to pull in to test this one bit.
+ hum.onEntryAdded(entryToPin);
+
+ assertEquals(2, mUiEventLoggerFake.numLogs());
+ assertEquals(AvalancheController.ThrottleEvent.SHOWN.getId(),
+ mUiEventLoggerFake.eventId(0));
+ assertEquals(BaseHeadsUpManager.NotificationPeekEvent.NOTIFICATION_PEEK.getId(),
+ mUiEventLoggerFake.eventId(1));
+ }
+
+ @Test
+ @DisableFlags(NotificationThrottleHun.FLAG_NAME)
+ public void testPinEntry_logsPeek_throttleDisabled() {
final BaseHeadsUpManager hum = createHeadsUpManager();
// Needs full screen intent in order to be pinned
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
index 9feb914..200e92e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java
@@ -167,7 +167,7 @@
mContext.getOrCreateTestableResources().addOverride(
R.integer.ambient_notification_extension_time, 500);
- mAvalancheController = new AvalancheController(dumpManager);
+ mAvalancheController = new AvalancheController(dumpManager, mUiEventLogger);
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
index b83b93b..10a4eb7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
@@ -50,6 +50,8 @@
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
+private const val builtInDeviceName = "This phone"
+
@OptIn(ExperimentalCoroutinesApi::class)
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -71,6 +73,10 @@
addOverride(R.drawable.ic_media_speaker_device, testIcon)
addOverride(com.android.internal.R.drawable.ic_bt_hearing_aid, testIcon)
+
+ addOverride(R.string.media_transfer_this_device_name_tv, builtInDeviceName)
+ addOverride(R.string.media_transfer_this_device_name_tablet, builtInDeviceName)
+ addOverride(R.string.media_transfer_this_device_name, builtInDeviceName)
}
}
}
@@ -90,7 +96,7 @@
assertThat(device).isInstanceOf(AudioOutputDevice.BuiltIn::class.java)
assertThat(device!!.icon).isEqualTo(testIcon)
- assertThat(device!!.name).isEqualTo("built_in")
+ assertThat(device!!.name).isEqualTo(builtInDeviceName)
}
}
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
new file mode 100644
index 0000000..8921a23
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorTest.kt
@@ -0,0 +1,141 @@
+/*
+ * 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.mediaoutput.domain.interactor
+
+import android.graphics.drawable.TestStubDrawable
+import android.media.AudioManager
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.data.repository.TestAudioDevicesFactory
+import com.android.systemui.volume.data.repository.audioRepository
+import com.android.systemui.volume.data.repository.audioSharingRepository
+import com.android.systemui.volume.domain.model.AudioOutputDevice
+import com.android.systemui.volume.localMediaController
+import com.android.systemui.volume.localMediaRepository
+import com.android.systemui.volume.mediaControllerRepository
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
+import com.android.systemui.volume.panel.shared.model.filterData
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val builtInDeviceName = "This phone"
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class MediaOutputComponentInteractorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private lateinit var underTest: MediaOutputComponentInteractor
+
+ @Before
+ fun setUp() =
+ with(kosmos) {
+ audioRepository.setMode(AudioManager.MODE_NORMAL)
+ localMediaRepository.updateCurrentConnectedDevice(
+ TestMediaDevicesFactory.builtInMediaDevice(deviceIcon = testIcon)
+ )
+
+ with(context.orCreateTestableResources) {
+ addOverride(R.drawable.ic_smartphone, testIcon)
+
+ addOverride(R.string.media_transfer_this_device_name_tv, builtInDeviceName)
+ addOverride(R.string.media_transfer_this_device_name_tablet, builtInDeviceName)
+ addOverride(R.string.media_transfer_this_device_name, builtInDeviceName)
+ }
+
+ underTest = mediaOutputComponentInteractor
+ }
+
+ @Test
+ fun inCall_stateIs_Calling() =
+ with(kosmos) {
+ testScope.runTest {
+ with(audioRepository) {
+ setMode(AudioManager.MODE_IN_CALL)
+ setCommunicationDevice(TestAudioDevicesFactory.builtInDevice())
+ }
+
+ val model by collectLastValue(underTest.mediaOutputModel.filterData())
+ runCurrent()
+
+ assertThat(model)
+ .isEqualTo(
+ MediaOutputComponentModel.Calling(
+ AudioOutputDevice.BuiltIn(builtInDeviceName, testIcon),
+ false,
+ )
+ )
+ }
+ }
+
+ @Test
+ fun hasSession_stateIs_MediaSession() =
+ with(kosmos) {
+ testScope.runTest {
+ mediaControllerRepository.setActiveSessions(listOf(localMediaController))
+
+ val model by collectLastValue(underTest.mediaOutputModel.filterData())
+ runCurrent()
+
+ with(model as MediaOutputComponentModel.MediaSession) {
+ assertThat(session.appLabel).isEqualTo("local_media_controller_label")
+ assertThat(session.packageName).isEqualTo("local.test.pkg")
+ assertThat(session.canAdjustVolume).isTrue()
+ assertThat(device)
+ .isEqualTo(AudioOutputDevice.BuiltIn("built_in_media", testIcon))
+ assertThat(isInAudioSharing).isFalse()
+ }
+ }
+ }
+
+ @Test
+ fun noMediaOrCall_stateIs_Idle() =
+ with(kosmos) {
+ testScope.runTest {
+ audioSharingRepository.setInAudioSharing(true)
+
+ val model by collectLastValue(underTest.mediaOutputModel.filterData())
+ runCurrent()
+
+ assertThat(model)
+ .isEqualTo(
+ MediaOutputComponentModel.Idle(
+ AudioOutputDevice.BuiltIn("built_in_media", testIcon),
+ true,
+ )
+ )
+ }
+ }
+
+ private companion object {
+ val testIcon = TestStubDrawable()
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
index d497b4a..86a20dc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelTest.kt
@@ -31,14 +31,11 @@
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.android.systemui.volume.data.repository.audioSharingRepository
-import com.android.systemui.volume.domain.interactor.audioModeInteractor
-import com.android.systemui.volume.domain.interactor.audioOutputInteractor
import com.android.systemui.volume.localMediaController
import com.android.systemui.volume.localMediaRepository
import com.android.systemui.volume.mediaControllerRepository
-import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.mediaOutputActionsInteractor
-import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaOutputComponentInteractor
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -66,10 +63,7 @@
applicationContext,
testScope.backgroundScope,
mediaOutputActionsInteractor,
- mediaDeviceSessionInteractor,
- audioOutputInteractor,
- audioModeInteractor,
- mediaOutputInteractor,
+ mediaOutputComponentInteractor,
uiEventLogger,
)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorTest.kt
new file mode 100644
index 0000000..7934b02
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/ui/navigation/VolumeNavigatorTest.kt
@@ -0,0 +1,95 @@
+/*
+ * 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.ui.navigation
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.uiEventLoggerFake
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.testKosmos
+import com.android.systemui.volume.domain.model.VolumePanelRoute
+import com.android.systemui.volume.panel.domain.interactor.volumePanelGlobalStateInteractor
+import com.android.systemui.volume.panel.ui.viewmodel.volumePanelViewModelFactory
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class VolumeNavigatorTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+
+ private val underTest: VolumeNavigator =
+ with(kosmos) {
+ VolumeNavigator(
+ testScope.backgroundScope,
+ testDispatcher,
+ mock {},
+ activityStarter,
+ volumePanelViewModelFactory,
+ mock {
+ on { create(any(), anyInt(), anyBoolean(), any()) }.thenReturn(mock {})
+ on { applicationContext }.thenReturn(context)
+ },
+ uiEventLoggerFake,
+ volumePanelGlobalStateInteractor,
+ )
+ }
+
+ @Test
+ fun showNewVolumePanel_keyguardLocked_notShown() =
+ with(kosmos) {
+ testScope.runTest {
+ val panelState by collectLastValue(volumePanelGlobalStateInteractor.globalState)
+
+ underTest.openVolumePanel(VolumePanelRoute.COMPOSE_VOLUME_PANEL)
+ runCurrent()
+
+ assertThat(panelState!!.isVisible).isFalse()
+ }
+ }
+
+ @Test
+ fun showNewVolumePanel_keyguardUnlocked_shown() =
+ with(kosmos) {
+ testScope.runTest {
+ whenever(activityStarter.dismissKeyguardThenExecute(any(), any(), anyBoolean()))
+ .then { (it.arguments[0] as ActivityStarter.OnDismissAction).onDismiss() }
+ val panelState by collectLastValue(volumePanelGlobalStateInteractor.globalState)
+
+ underTest.openVolumePanel(VolumePanelRoute.COMPOSE_VOLUME_PANEL)
+ runCurrent()
+
+ assertThat(panelState!!.isVisible).isTrue()
+ }
+ }
+}
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
index 3244eb4..bf58eee 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/qs/QS.java
@@ -94,6 +94,9 @@
default void setHasNotifications(boolean hasNotifications) {
}
+ /** Sets whether the squishiness fraction should be updated on the media host. */
+ default void setShouldUpdateSquishinessOnMedia(boolean shouldUpdate) {}
+
/**
* Should touches from the notification panel be disallowed?
* The notification panel might grab any touches rom QS at any time to collapse the shade.
diff --git a/packages/SystemUI/res-keyguard/drawable/ic_spatial_speaker.xml b/packages/SystemUI/res-keyguard/drawable/ic_spatial_speaker.xml
new file mode 100644
index 0000000..82b222e7
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/ic_spatial_speaker.xml
@@ -0,0 +1,25 @@
+<!--
+ ~ 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="24dp"
+ android:height="24dp"
+ android:viewportHeight="960"
+ android:viewportWidth="960">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M360,880q-134,0 -227,-93T40,560h80q0,100 70,170t170,70v80ZM360,740q-75,0 -127.5,-52.5T180,560h80q0,42 29,71t71,29v80ZM400,600q-33,0 -56.5,-23.5T320,520v-320q0,-33 23.5,-56.5T400,120h160q33,0 56.5,23.5T640,200v320q0,33 -23.5,56.5T560,600L400,600ZM400,520h160v-320L400,200v320ZM600,740v-80q42,0 71,-29t29,-71h80q0,75 -52.5,127.5T600,740ZM600,880v-80q100,0 170,-70t70,-170h80q0,134 -93,227T600,880ZM400,520h160,-160Z" />
+</vector>
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions.xml b/packages/SystemUI/res-keyguard/layout/footer_actions.xml
deleted file mode 100644
index 4a2a1cb..0000000
--- a/packages/SystemUI/res-keyguard/layout/footer_actions.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 2022, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
--->
-
-<!-- Action buttons for footer in QS/QQS, containing settings button, power off button etc -->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="@dimen/footer_actions_height"
- android:elevation="@dimen/qs_panel_elevation"
- android:paddingTop="@dimen/qs_footer_actions_top_padding"
- android:paddingBottom="@dimen/qs_footer_actions_bottom_padding"
- android:background="@drawable/qs_footer_actions_background"
- android:gravity="center_vertical|end"
- android:layout_gravity="bottom"
-/>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml
deleted file mode 100644
index fad41c82..0000000
--- a/packages/SystemUI/res-keyguard/layout/footer_actions_icon_button.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2022 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/qs_footer_action_button_size"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:visibility="gone">
- <ImageView
- android:id="@+id/icon"
- android:layout_width="@dimen/qs_footer_icon_size"
- android:layout_height="@dimen/qs_footer_icon_size"
- android:layout_gravity="center"
- android:scaleType="centerInside" />
-</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
deleted file mode 100644
index c09607d..0000000
--- a/packages/SystemUI/res-keyguard/layout/footer_actions_number_button.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2022 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="@dimen/qs_footer_action_button_size"
- android:layout_height="@dimen/qs_footer_action_button_size"
- android:background="@drawable/qs_footer_action_circle"
- android:visibility="gone">
- <TextView
- android:id="@+id/number"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
- android:layout_gravity="center"
- android:textColor="?attr/onShadeInactiveVariant"
- android:textSize="18sp"/>
- <ImageView
- android:id="@+id/new_dot"
- android:layout_width="12dp"
- android:layout_height="12dp"
- android:scaleType="fitCenter"
- android:layout_gravity="bottom|end"
- android:src="@drawable/fgs_dot"
- android:contentDescription="@string/fgs_dot_content_description" />
-</com.android.systemui.statusbar.AlphaOptimizedFrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml b/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
deleted file mode 100644
index 1c31f1d..0000000
--- a/packages/SystemUI/res-keyguard/layout/footer_actions_text_button.xml
+++ /dev/null
@@ -1,66 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2022 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<com.android.systemui.animation.view.LaunchableLinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="0dp"
- android:layout_height="@dimen/qs_security_footer_single_line_height"
- android:layout_weight="1"
- android:orientation="horizontal"
- android:paddingHorizontal="@dimen/qs_footer_padding"
- android:gravity="center_vertical"
- android:layout_marginEnd="@dimen/qs_footer_action_inset"
- android:background="@drawable/qs_security_footer_background"
- android:visibility="gone">
- <ImageView
- android:id="@+id/icon"
- android:layout_width="@dimen/qs_footer_icon_size"
- android:layout_height="@dimen/qs_footer_icon_size"
- android:gravity="start"
- android:layout_marginEnd="12dp"
- android:contentDescription="@null"
- android:src="@drawable/ic_info_outline"
- android:tint="?attr/onSurfaceVariant" />
-
- <TextView
- android:id="@+id/text"
- android:layout_width="0dp"
- android:layout_height="wrap_content"
- android:layout_weight="1"
- android:maxLines="1"
- android:ellipsize="end"
- android:textAppearance="@style/TextAppearance.QS.SecurityFooter"
- android:textColor="?attr/onSurfaceVariant"/>
-
- <ImageView
- android:id="@+id/new_dot"
- android:layout_width="12dp"
- android:layout_height="12dp"
- android:scaleType="fitCenter"
- android:src="@drawable/fgs_dot"
- android:contentDescription="@string/fgs_dot_content_description"
- />
-
- <ImageView
- android:id="@+id/chevron_icon"
- android:layout_width="@dimen/qs_footer_icon_size"
- android:layout_height="@dimen/qs_footer_icon_size"
- android:layout_marginStart="8dp"
- android:contentDescription="@null"
- android:src="@*android:drawable/ic_chevron_end"
- android:autoMirrored="true"
- android:tint="?attr/onSurfaceVariant" />
-</com.android.systemui.animation.view.LaunchableLinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml b/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.xml
deleted file mode 100644
index a8abd79..0000000
--- a/packages/SystemUI/res/color/qs_footer_power_button_overlay_color.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.
- -->
-
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_pressed="true" android:color="?attr/onShadeActive" android:alpha="0.12" />
- <item android:state_hovered="true" android:color="?attr/onShadeActive" android:alpha="0.09" />
- <item android:color="@color/transparent" />
-</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/fgs_dot.xml b/packages/SystemUI/res/drawable/fgs_dot.xml
deleted file mode 100644
index 0881d7c..0000000
--- a/packages/SystemUI/res/drawable/fgs_dot.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-** Copyright 2022, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-** http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-** See the License for the specific language governing permissions and
-** limitations under the License.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="oval"
- android:width="12dp"
- android:height="12dp">
- <solid android:color="?attr/tertiary" />
-</shape>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
deleted file mode 100644
index 4a5d4af..0000000
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:inset="@dimen/qs_footer_action_inset">
- <ripple
- android:color="?android:attr/colorControlHighlight">
- <item android:id="@android:id/mask">
- <!-- We make this shape a rounded rectangle instead of a oval so that it can animate -->
- <!-- properly into an app/dialog. -->
- <shape android:shape="rectangle">
- <solid android:color="@android:color/white"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <solid android:color="?attr/shadeInactive"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
-
- </ripple>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml b/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
deleted file mode 100644
index 47a2965..0000000
--- a/packages/SystemUI/res/drawable/qs_footer_action_circle_color.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:inset="@dimen/qs_footer_action_inset">
- <ripple
- android:color="?android:attr/colorControlHighlight">
- <item android:id="@android:id/mask">
- <!-- We make this shape a rounded rectangle instead of a oval so that it can animate -->
- <!-- properly into an app/dialog. -->
- <shape android:shape="rectangle">
- <solid android:color="@android:color/white"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <solid android:color="?attr/shadeActive"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <solid android:color="@color/qs_footer_power_button_overlay_color"/>
- <corners android:radius="@dimen/qs_footer_action_corner_radius"/>
- </shape>
- </item>
-
- </ripple>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/qs_security_footer_background.xml b/packages/SystemUI/res/drawable/qs_security_footer_background.xml
deleted file mode 100644
index 0b0055b..0000000
--- a/packages/SystemUI/res/drawable/qs_security_footer_background.xml
+++ /dev/null
@@ -1,37 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetTop="@dimen/qs_footer_action_inset"
- android:insetBottom="@dimen/qs_footer_action_inset"
- >
- <ripple
- android:color="?android:attr/colorControlHighlight">
- <item android:id="@android:id/mask">
- <shape android:shape="rectangle">
- <solid android:color="@android:color/white"/>
- <corners android:radius="@dimen/qs_security_footer_corner_radius"/>
- </shape>
- </item>
- <item>
- <shape android:shape="rectangle">
- <stroke android:width="1dp"
- android:color="?attr/shadeInactive"/>
- <corners android:radius="@dimen/qs_security_footer_corner_radius"/>
- </shape>
- </item>
- </ripple>
-</inset>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml b/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml
deleted file mode 100644
index 29a014a..0000000
--- a/packages/SystemUI/res/drawable/rounded_bg_full_large_radius.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorAccentPrimary" />
- <corners android:radius="40dp" />
-</shape>
diff --git a/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml b/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml
index 292e496..06d1bf4 100644
--- a/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml
+++ b/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml
@@ -5,9 +5,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <LinearLayout
+ <FrameLayout
android:id="@+id/shortcut_helper_sheet"
- style="@style/Widget.Material3.BottomSheet"
+ style="@style/ShortcutHelperBottomSheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
@@ -19,13 +19,9 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" />
- <TextView
+ <androidx.compose.ui.platform.ComposeView
+ android:id="@+id/shortcut_helper_compose_container"
android:layout_width="match_parent"
- android:layout_height="0dp"
- android:layout_weight="1"
- android:gravity="center"
- android:textAppearance="?textAppearanceDisplayLarge"
- android:background="?colorTertiaryContainer"
- android:text="Shortcut Helper Content" />
- </LinearLayout>
-</androidx.coordinatorlayout.widget.CoordinatorLayout>
+ android:layout_height="match_parent" />
+ </FrameLayout>
+</androidx.coordinatorlayout.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_activity.xml b/packages/SystemUI/res/layout/people_space_activity.xml
deleted file mode 100644
index f45cc7c..0000000
--- a/packages/SystemUI/res/layout/people_space_activity.xml
+++ /dev/null
@@ -1,23 +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.
- -->
-<FrameLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/container"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <!-- The content of people_space_activity_(no|with)_conversations.xml will be added here at
- runtime depending on the number of conversations to show. -->
-</FrameLayout>
diff --git a/packages/SystemUI/res/layout/people_space_activity_list_divider.xml b/packages/SystemUI/res/layout/people_space_activity_list_divider.xml
deleted file mode 100644
index 3b9fb3b..0000000
--- a/packages/SystemUI/res/layout/people_space_activity_list_divider.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<View
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="2dp"
- android:background="?android:attr/colorBackground" />
diff --git a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
deleted file mode 100644
index a97c90c..0000000
--- a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
+++ /dev/null
@@ -1,79 +0,0 @@
-<!--
- ~ Copyright (C) 2021 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<RelativeLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:id="@+id/top_level_no_conversations"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:padding="24dp"
- android:clipToOutline="true">
- <TextView
- android:id="@+id/select_conversation_title"
- android:gravity="center"
- android:text="@string/select_conversation_title"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_centerHorizontal="true"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="24sp"
- android:layout_alignParentTop="true" />
-
- <TextView
- android:id="@+id/select_conversation"
- android:gravity="center"
- android:text="@string/no_conversations_text"
- android:layout_width="match_parent"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="16sp"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:padding="24dp"
- android:layout_marginTop="26dp"
- android:layout_below="@id/select_conversation_title"/>
-
- <Button
- style="?android:attr/buttonBarButtonStyle"
- android:id="@+id/got_it_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:background="@drawable/rounded_bg_full_large_radius"
- android:text="@string/got_it"
- android:textColor="?androidprv:attr/textColorOnAccent"
- android:layout_marginBottom="60dp"
- android:layout_alignParentBottom="true" />
-
- <LinearLayout
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_above="@id/got_it_button"
- android:layout_below="@id/select_conversation"
- android:layout_centerInParent="true"
- android:clipToOutline="true">
- <LinearLayout
- android:id="@+id/widget_initial_layout"
- android:layout_width="200dp"
- android:layout_height="100dp"
- android:layout_gravity="center"
- android:background="@drawable/rounded_bg_full_large_radius"
- android:layout_above="@id/got_it_button">
- <include layout="@layout/people_space_placeholder_layout" />
- </LinearLayout>
- </LinearLayout>
-</RelativeLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml
deleted file mode 100644
index 2384963..0000000
--- a/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml
+++ /dev/null
@@ -1,115 +0,0 @@
-<!--
- ~ Copyright (C) 2022 The Android Open Source Project
- ~
- ~ Licensed under the Apache License, Version 2.0 (the "License");
- ~ you may not use this file except in compliance with the License.
- ~ You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:id="@+id/top_level_with_conversations"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:padding="8dp">
- <TextView
- android:id="@+id/select_conversation_title"
- android:text="@string/select_conversation_title"
- android:gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="24sp"/>
-
- <TextView
- android:id="@+id/select_conversation"
- android:text="@string/select_conversation_text"
- android:gravity="center"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="16sp"
- android:paddingVertical="24dp"
- android:paddingHorizontal="48dp"/>
-
- <androidx.core.widget.NestedScrollView
- android:id="@+id/scroll_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <LinearLayout
- android:id="@+id/scroll_layout"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/priority"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="35dp">
- <TextView
- android:id="@+id/priority_header"
- android:text="@string/priority_conversations"
- android:layout_width="wrap_content"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
- android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
- android:textSize="14sp"
- android:paddingStart="16dp"
- android:layout_height="wrap_content"/>
-
- <LinearLayout
- android:id="@+id/priority_tiles"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:orientation="vertical"
- android:background="@drawable/rounded_bg_full_large_radius"
- android:clipToOutline="true">
- </LinearLayout>
- </LinearLayout>
-
- <LinearLayout
- android:id="@+id/recent"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <TextView
- android:id="@+id/recent_header"
- android:gravity="start"
- android:text="@string/recent_conversations"
- android:layout_width="wrap_content"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
- android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
- android:textSize="14sp"
- android:paddingStart="16dp"
- android:layout_height="wrap_content"/>
-
- <LinearLayout
- android:id="@+id/recent_tiles"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:orientation="vertical"
- android:background="@drawable/rounded_bg_full_large_radius"
- android:clipToOutline="true">
- </LinearLayout>
- </LinearLayout>
- </LinearLayout>
- </androidx.core.widget.NestedScrollView>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_tile_view.xml b/packages/SystemUI/res/layout/people_space_tile_view.xml
deleted file mode 100644
index b0599ca..0000000
--- a/packages/SystemUI/res/layout/people_space_tile_view.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- ~ 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.
- -->
-<LinearLayout
- xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:id="@+id/tile_view"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <LinearLayout
- android:orientation="vertical"
- android:background="?androidprv:attr/colorSurface"
- android:padding="12dp"
- android:elevation="4dp"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="start">
-
- <ImageView
- android:id="@+id/tile_view_person_icon"
- android:layout_width="@dimen/avatar_size_for_medium"
- android:layout_height="@dimen/avatar_size_for_medium" />
-
- <LinearLayout
- android:orientation="horizontal"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical">
-
- <TextView
- android:id="@+id/tile_view_name"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:paddingHorizontal="16dp"
- android:textSize="22sp"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"/>
- </LinearLayout>
- </LinearLayout>
- </LinearLayout>
-</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/qs_panel.xml b/packages/SystemUI/res/layout/qs_panel.xml
index e3c5a7d..5f77f61 100644
--- a/packages/SystemUI/res/layout/qs_panel.xml
+++ b/packages/SystemUI/res/layout/qs_panel.xml
@@ -47,13 +47,12 @@
<include layout="@layout/quick_status_bar_expanded_header" />
- <include
- layout="@layout/footer_actions"
+ <androidx.compose.ui.platform.ComposeView
android:id="@+id/qs_footer_actions"
android:layout_height="@dimen/footer_actions_height"
android:layout_width="match_parent"
android:layout_gravity="bottom"
- />
+ android:elevation="@dimen/qs_panel_elevation" />
<include
android:id="@+id/qs_customize"
diff --git a/packages/SystemUI/res/raw/face_dialog_authenticating.json b/packages/SystemUI/res/raw/face_dialog_authenticating.json
deleted file mode 100644
index 4e25e6d..0000000
--- a/packages/SystemUI/res/raw/face_dialog_authenticating.json
+++ /dev/null
@@ -1 +0,0 @@
-{"v":"5.7.13","fr":60,"ip":0,"op":61,"w":64,"h":64,"nm":"face_scanning 3","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".blue200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[32,32,0],"ix":2,"l":2},"a":{"a":0,"k":[27.25,27.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[100,100,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":30,"s":[95,95,100]},{"t":60,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.243],[-1.244,0],[0,1.243],[1.242,0]],"o":[[0,1.243],[1.242,0],[0,-1.243],[-1.244,0]],"v":[[-2.249,0.001],[0.001,2.251],[2.249,0.001],[0.001,-2.251]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[15.1,20.495],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-1.243],[-1.242,0],[0,1.243],[1.242,0]],"o":[[0,1.243],[1.242,0],[0,-1.243],[-1.242,0]],"v":[[-2.249,0],[0.001,2.25],[2.249,0],[0.001,-2.25]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[39.4,20.495],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,0]],"v":[[2.814,3.523],[-2.814,3.523],[-2.814,1.363],[0.652,1.363],[0.652,-3.523],[2.814,-3.523]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.791,28.479],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.154,0.15],[0,0],[0.117,-0.095],[0,0],[0.228,-0.121],[0.358,-0.103],[0.922,0.261],[0.3,0.16],[0.24,0.185],[0.14,0.139],[0.178,0.261],[0.143,0.451],[0,0],[0,0.494],[0,0],[-0.214,-0.676],[-0.392,-0.572],[-0.323,-0.317],[-0.228,-0.177],[-0.333,-0.179],[-0.503,-0.145],[-0.662,0],[-0.653,0.184],[-0.437,0.233],[-0.336,0.258],[0,0],[0,0]],"o":[[0,0],[-0.107,0.106],[0,0],[-0.24,0.185],[-0.301,0.16],[-0.92,0.261],[-0.357,-0.103],[-0.228,-0.121],[-0.158,-0.122],[-0.225,-0.221],[-0.272,-0.393],[0,0],[-0.147,-0.466],[0,0],[0,0.716],[0.206,0.656],[0.256,0.372],[0.204,0.201],[0.336,0.258],[0.436,0.233],[0.655,0.184],[0.662,0],[0.503,-0.145],[0.332,-0.179],[0,0],[0,0],[0.165,-0.136]],"v":[[6.094,1.465],[4.579,-0.076],[4.242,0.225],[4.124,0.315],[3.43,0.771],[2.439,1.165],[-0.342,1.165],[-1.331,0.771],[-2.027,0.315],[-2.48,-0.075],[-3.087,-0.801],[-3.712,-2.075],[-3.712,-2.075],[-3.934,-3.523],[-6.094,-3.523],[-5.771,-1.424],[-4.868,0.424],[-3.995,1.465],[-3.344,2.027],[-2.35,2.676],[-0.934,3.243],[1.049,3.523],[3.031,3.243],[4.449,2.676],[5.441,2.027],[5.482,1.997],[5.615,1.895]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[26.201,40.411],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-13.398,0],[0,-13.4],[13.398,0],[0,13.4]],"o":[[13.398,0],[0,13.4],[-13.398,0],[0,-13.4]],"v":[[0,-24.3],[24.3,0],[0,24.3],[-24.3,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[14.904,0],[0,-14.904],[-14.904,0],[0,14.904]],"o":[[-14.904,0],[0,14.904],[14.904,0],[0,-14.904]],"v":[[0,-27],[-27,0],[0,27],[27,0]],"c":true},"ix":2},"nm":"Path 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.658823529412,0.780392216701,0.980392216701,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":30,"s":[60]},{"t":60,"s":[100]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[27.25,27.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 5","np":4,"cix":2,"bm":0,"ix":5,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":1200,"st":0,"bm":0}],"markers":[]}
\ No newline at end of file
diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml
index 56ebc06..aea79e8 100644
--- a/packages/SystemUI/res/values-land/dimens.xml
+++ b/packages/SystemUI/res/values-land/dimens.xml
@@ -28,9 +28,7 @@
<!-- In landscape the security footer is actually part of the header,
and needs to be as short as the header -->
- <dimen name="qs_security_footer_single_line_height">@*android:dimen/quick_qs_offset_height</dimen>
<dimen name="qs_footer_padding">14dp</dimen>
- <dimen name="qs_security_footer_background_inset">12dp</dimen>
<dimen name="volume_tool_tip_top_margin">12dp</dimen>
<dimen name="volume_row_slider_height">128dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp/dimens.xml b/packages/SystemUI/res/values-sw600dp/dimens.xml
index 664fbeb..29e0dbe 100644
--- a/packages/SystemUI/res/values-sw600dp/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp/dimens.xml
@@ -68,11 +68,6 @@
<dimen name="qs_brightness_margin_bottom">16dp</dimen>
- <!-- For large screens the security footer appears below the footer,
- same as phones in portrait -->
- <dimen name="qs_security_footer_single_line_height">48dp</dimen>
- <dimen name="qs_security_footer_background_inset">0dp</dimen>
-
<dimen name="qs_panel_padding_top">8dp</dimen>
<!-- The width of large/content heavy dialogs (e.g. Internet, Media output, etc) -->
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 380a79e..8ce2068 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -713,7 +713,6 @@
<dimen name="qs_header_mobile_icon_size">@dimen/status_bar_icon_drawing_size</dimen>
<dimen name="qs_header_carrier_separator_width">6dp</dimen>
<dimen name="qs_carrier_margin_width">4dp</dimen>
- <dimen name="qs_footer_icon_size">20dp</dimen>
<dimen name="qs_header_height">120dp</dimen>
<dimen name="qs_header_row_min_height">48dp</dimen>
@@ -721,11 +720,7 @@
<dimen name="new_qs_header_non_clickable_element_height">24sp</dimen>
<dimen name="qs_footer_padding">20dp</dimen>
- <dimen name="qs_security_footer_height">88dp</dimen>
- <dimen name="qs_security_footer_single_line_height">48dp</dimen>
<dimen name="qs_footers_margin_bottom">8dp</dimen>
- <dimen name="qs_security_footer_background_inset">0dp</dimen>
- <dimen name="qs_security_footer_corner_radius">28dp</dimen>
<dimen name="segmented_button_spacing">0dp</dimen>
<dimen name="borderless_button_radius">2dp</dimen>
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index b993a5a..177ba598 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -259,9 +259,6 @@
<!-- ID of the Scene Container root Composable view -->
<item type='id' name="scene_container_root_composable" />
- <!-- Tag set on the Compose implementation of the QS footer actions. -->
- <item type="id" name="tag_compose_qs_footer_actions" />
-
<!--
Ids for the device entry icon.
device_entry_icon_view: parent view of both device_entry_icon and device_entry_icon_bg
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c038a82..1226bbf 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3489,6 +3489,45 @@
<!-- Label for recent app usage of a phone sensor with sub-attribution and proxy label in the privacy dialog [CHAR LIMIT=NONE] -->
<string name="privacy_dialog_recent_app_usage_2">Recently used by <xliff:g id="app_name" example="Gmail">%1$s</xliff:g> (<xliff:g id="attribution_label" example="For Wallet">%2$s</xliff:g> \u2022 <xliff:g id="proxy_label" example="Speech services">%3$s</xliff:g>)</string>
+ <!-- Title of the keyboard shortcut helper category "System". The helper is a component that
+ shows the user which keyboard shortcuts they can use. The "System" shortcuts are for
+ example "Take a screenshot" or "Go back". [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_category_system">System</string>
+ <!-- Title of the keyboard shortcut helper category "Multitasking". The helper is a component
+ that shows the user which keyboard shortcuts they can use. The "Multitasking" shortcuts are
+ for example "Enter split screen". [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_category_multitasking">Multitasking</string>
+ <!-- Title of the keyboard shortcut helper category "Input". The helper is a component
+ that shows the user which keyboard shortcuts they can use. The "Input" shortcuts are
+ the ones provided by the keyboard. Examples are "Access emoji" or "Switch to next language"
+ [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_category_input">Input</string>
+ <!-- Title of the keyboard shortcut helper category "App shortcuts". The helper is a component
+ that shows the user which keyboard shortcuts they can use. The "App shortcuts" are
+ for example "Open browser" or "Open calculator". [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_category_app_shortcuts">App shortcuts</string>
+ <!-- Title of the keyboard shortcut helper category "Accessibility". The helper is a component
+ that shows the user which keyboard shortcuts they can use. The "Accessibility" shortcuts
+ are for example "Turn on talkback". [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_category_a11y">Accessibility</string>
+ <!-- Title at the top of the keyboard shortcut helper UI. The helper is a component
+ that shows the user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_title">Keyboard shortcuts</string>
+ <!-- Placeholder text shown in the search box of the keyboard shortcut helper, when the user
+ hasn't typed in anything in the search box yet. The helper is a component that shows the
+ user which keyboard shortcuts they can use. [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_search_placeholder">Search shortcuts</string>
+ <!-- Content description of the icon that allows to collapse a keyboard shortcut helper category
+ panel. The helper is a component that shows the user which keyboard shortcuts they can
+ use. The helper shows shortcuts in categories, which can be collapsed or expanded.
+ [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_content_description_collapse_icon">Collapse icon</string>
+ <!-- Content description of the icon that allows to expand a keyboard shortcut helper category
+ panel. The helper is a component that shows the user which keyboard shortcuts they can
+ use. The helper shows shortcuts in categories, which can be collapsed or expanded.
+ [CHAR LIMIT=NONE] -->
+ <string name="shortcut_helper_content_description_expand_icon">Expand icon</string>
+
<!-- Content description for keyboard backlight brightness dialog [CHAR LIMIT=NONE] -->
<string name="keyboard_backlight_dialog_title">Keyboard backlight</string>
<!-- Content description for keyboard backlight brightness value [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index b8f71c1..1e0adec 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -149,11 +149,6 @@
<item name="android:letterSpacing">0.01</item>
</style>
- <style name="TextAppearance.QS.SecurityFooter" parent="@style/TextAppearance.QS.Status">
- <item name="android:fontFamily">@*android:string/config_bodyFontFamily</item>
- <item name="android:textColor">?attr/onSurface</item>
- </style>
-
<style name="TextAppearance.QS.Status.Carriers" />
<style name="TextAppearance.QS.Status.Carriers.NoCarrierText">
@@ -1670,6 +1665,10 @@
<item name="android:colorBackground">@color/transparent</item>
</style>
+ <style name="ShortcutHelperBottomSheet" parent="@style/Widget.Material3.BottomSheet">
+ <item name="backgroundTint">?colorSurfaceContainer</item>
+ </style>
+
<style name="ShortcutHelperAnimation" parent="@android:style/Animation.Activity">
<item name="android:activityOpenEnterAnimation">@anim/shortcut_helper_launch_anim</item>
<item name="android:taskOpenEnterAnimation">@anim/shortcut_helper_launch_anim</item>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index 1366226..e8eb53f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -17,6 +17,7 @@
package com.android.systemui.flags
import android.app.Activity
+import android.content.pm.PackageManager
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -35,6 +36,7 @@
) : FlagListenable {
companion object {
const val RECEIVING_PACKAGE = "com.android.systemui"
+ const val RECEIVING_PACKAGE_WATCH = "com.google.android.apps.wearable.systemui"
const val ACTION_SET_FLAG = "com.android.systemui.action.SET_FLAG"
const val ACTION_GET_FLAGS = "com.android.systemui.action.GET_FLAGS"
const val FLAGS_PERMISSION = "com.android.systemui.permission.FLAGS"
@@ -62,7 +64,7 @@
fun getFlagsFuture(): ListenableFuture<Collection<Flag<*>>> {
val intent = Intent(ACTION_GET_FLAGS)
- intent.setPackage(RECEIVING_PACKAGE)
+ intent.setPackage(if (isWatch()) RECEIVING_PACKAGE_WATCH else RECEIVING_PACKAGE)
return CallbackToFutureAdapter.getFuture {
completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> ->
@@ -193,6 +195,10 @@
restartAction?.accept(suppressRestart)
}
+ private fun isWatch(): Boolean {
+ return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+ }
+
private data class PerFlagListener(val name: String, val listener: FlagListenable.Listener)
}
diff --git a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
index 3bf3fb3..b116e29 100644
--- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java
@@ -308,7 +308,13 @@
} else {
// Don't listen and clear out the text when the device isn't a phone.
mMainExecutor.execute(() -> callback.updateCarrierInfo(
- new CarrierTextCallbackInfo("", null, false, null)
+ new CarrierTextCallbackInfo(
+ /* carrierText= */ "",
+ /* listOfCarriers= */ null,
+ /* anySimReady= */ false,
+ /* isInSatelliteMode= */ false,
+ /* subscriptionIds= */ null,
+ /* airplaneMode= */ false)
));
}
} else {
@@ -448,10 +454,12 @@
displayText = currentSatelliteText;
}
+ boolean isInSatelliteMode = mSatelliteCarrierText != null;
final CarrierTextCallbackInfo info = new CarrierTextCallbackInfo(
displayText,
carrierNames,
!allSimsMissing,
+ isInSatelliteMode,
subsIds,
airplaneMode);
mLogger.logCallbackSentFromUpdate(info);
@@ -757,21 +765,35 @@
public final CharSequence carrierText;
public final CharSequence[] listOfCarriers;
public final boolean anySimReady;
+ public final boolean isInSatelliteMode;
public final int[] subscriptionIds;
public boolean airplaneMode;
@VisibleForTesting
- public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
- boolean anySimReady, int[] subscriptionIds) {
- this(carrierText, listOfCarriers, anySimReady, subscriptionIds, false);
+ public CarrierTextCallbackInfo(
+ CharSequence carrierText,
+ CharSequence[] listOfCarriers,
+ boolean anySimReady,
+ int[] subscriptionIds) {
+ this(carrierText,
+ listOfCarriers,
+ anySimReady,
+ /* isInSatelliteMode= */ false,
+ subscriptionIds,
+ /* airplaneMode= */ false);
}
- @VisibleForTesting
- public CarrierTextCallbackInfo(CharSequence carrierText, CharSequence[] listOfCarriers,
- boolean anySimReady, int[] subscriptionIds, boolean airplaneMode) {
+ public CarrierTextCallbackInfo(
+ CharSequence carrierText,
+ CharSequence[] listOfCarriers,
+ boolean anySimReady,
+ boolean isInSatelliteMode,
+ int[] subscriptionIds,
+ boolean airplaneMode) {
this.carrierText = carrierText;
this.listOfCarriers = listOfCarriers;
this.anySimReady = anySimReady;
+ this.isInSatelliteMode = isInSatelliteMode;
this.subscriptionIds = subscriptionIds;
this.airplaneMode = airplaneMode;
}
@@ -782,6 +804,7 @@
+ "carrierText=" + carrierText
+ ", listOfCarriers=" + Arrays.toString(listOfCarriers)
+ ", anySimReady=" + anySimReady
+ + ", isInSatelliteMode=" + isInSatelliteMode
+ ", subscriptionIds=" + Arrays.toString(subscriptionIds)
+ ", airplaneMode=" + airplaneMode
+ '}';
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
index a74b0b0..b8ff3bb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/FingerprintPropertyInteractor.kt
@@ -98,11 +98,11 @@
) { unscaledSensorLocation, scale ->
val sensorLocation =
SensorLocation(
- unscaledSensorLocation.sensorLocationX,
- unscaledSensorLocation.sensorLocationY,
- unscaledSensorLocation.sensorRadius,
+ naturalCenterX = unscaledSensorLocation.sensorLocationX,
+ naturalCenterY = unscaledSensorLocation.sensorLocationY,
+ naturalRadius = unscaledSensorLocation.sensorRadius,
+ scale = scale
)
- sensorLocation.scale = scale
sensorLocation
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt
index dddadbd..2f2f3a3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorLocation.kt
@@ -16,18 +16,18 @@
package com.android.systemui.biometrics.shared.model
-/** Provides current sensor location information in the current screen resolution [scale]. */
+/**
+ * Provides current sensor location information in the current screen resolution [scale].
+ *
+ * @property scale Scale to apply to the sensor location's natural parameters to support different
+ * screen resolutions.
+ */
data class SensorLocation(
private val naturalCenterX: Int,
private val naturalCenterY: Int,
- private val naturalRadius: Int
+ private val naturalRadius: Int,
+ private val scale: Float = 1f
) {
- /**
- * Scale to apply to the sensor location's natural parameters to support different screen
- * resolutions.
- */
- var scale: Float = 1f
-
val centerX: Float
get() {
return naturalCenterX * scale
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 fcc6992..9e836c3 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
@@ -17,7 +17,9 @@
package com.android.systemui.biometrics.ui.binder
+import android.graphics.drawable.Animatable2
import android.graphics.drawable.AnimatedVectorDrawable
+import android.graphics.drawable.Drawable
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
import com.airbnb.lottie.LottieAnimationView
@@ -28,8 +30,8 @@
import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel.AuthType
import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.res.R
import com.android.systemui.util.kotlin.Utils.Companion.toQuad
+import com.android.systemui.util.kotlin.Utils.Companion.toQuint
import com.android.systemui.util.kotlin.Utils.Companion.toTriple
import com.android.systemui.util.kotlin.sample
import kotlinx.coroutines.flow.combine
@@ -61,6 +63,16 @@
}
var faceIcon: AnimatedVectorDrawable? = null
+ val faceIconCallback =
+ object : Animatable2.AnimationCallback() {
+ override fun onAnimationStart(drawable: Drawable) {
+ viewModel.onAnimationStart()
+ }
+
+ override fun onAnimationEnd(drawable: Drawable) {
+ viewModel.onAnimationEnd()
+ }
+ }
if (!constraintBp()) {
launch {
@@ -126,13 +138,19 @@
combine(
viewModel.activeAuthType,
viewModel.shouldAnimateIconView,
+ viewModel.shouldRepeatAnimation,
viewModel.showingError,
- ::Triple
+ ::toQuad
),
- ::toQuad
+ ::toQuint
)
- .collect { (iconAsset, activeAuthType, shouldAnimateIconView, showingError)
- ->
+ .collect {
+ (
+ iconAsset,
+ activeAuthType,
+ shouldAnimateIconView,
+ shouldRepeatAnimation,
+ showingError) ->
if (iconAsset != -1) {
when (activeAuthType) {
AuthType.Fingerprint,
@@ -145,27 +163,21 @@
}
}
AuthType.Face -> {
- // TODO(b/318569643): Consolidate logic once all face auth
- // assets are migrated from drawable to json
- if (iconAsset == R.raw.face_dialog_authenticating) {
- iconView.setAnimation(iconAsset)
- iconView.frame = 0
-
+ faceIcon?.apply {
+ unregisterAnimationCallback(faceIconCallback)
+ stop()
+ }
+ faceIcon =
+ iconView.context.getDrawable(iconAsset)
+ as AnimatedVectorDrawable
+ faceIcon?.apply {
+ iconView.setImageDrawable(this)
if (shouldAnimateIconView) {
- iconView.playAnimation()
- iconView.loop(true)
- }
- } else {
- faceIcon?.apply { stop() }
- faceIcon =
- iconView.context.getDrawable(iconAsset)
- as AnimatedVectorDrawable
- faceIcon?.apply {
- iconView.setImageDrawable(this)
- if (shouldAnimateIconView) {
- forceAnimationOnUI()
- start()
+ forceAnimationOnUI()
+ if (shouldRepeatAnimation) {
+ registerAnimationCallback(faceIconCallback)
}
+ start()
}
}
}
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 901d751..bde3e99 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
@@ -21,6 +21,7 @@
import android.annotation.RawRes
import android.content.res.Configuration
import android.graphics.Rect
+import android.hardware.face.Face
import android.util.RotationUtils
import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
@@ -31,10 +32,12 @@
import com.android.systemui.util.kotlin.combine
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
/**
* Models UI of [BiometricPromptLayout.iconView] and [BiometricPromptLayout.biometric_icon_overlay]
@@ -55,8 +58,11 @@
}
/**
- * Indicates what auth type the UI currently displays. Fingerprint-only auth -> Fingerprint
- * Face-only auth -> Face Co-ex auth, implicit flow -> Face Co-ex auth, explicit flow -> Coex
+ * Indicates what auth type the UI currently displays.
+ * Fingerprint-only auth -> Fingerprint
+ * Face-only auth -> Face
+ * Co-ex auth, implicit flow -> Face
+ * Co-ex auth, explicit flow -> Coex
*/
val activeAuthType: Flow<AuthType> =
combine(
@@ -113,6 +119,35 @@
_previousIconOverlayWasError.value = previousIconOverlayWasError
}
+ /** Called when iconView begins animating. */
+ fun onAnimationStart() {
+ _animationEnded.value = false
+ }
+
+ /** Called when iconView ends animating. */
+ fun onAnimationEnd() {
+ _animationEnded.value = true
+ }
+
+ private val _animationEnded: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /**
+ * Whether a face iconView should pulse (i.e. while isAuthenticating and previous animation
+ * ended).
+ */
+ val shouldPulseAnimation: Flow<Boolean> =
+ combine(_animationEnded, promptViewModel.isAuthenticating) {
+ animationEnded,
+ isAuthenticating ->
+ animationEnded && isAuthenticating
+ }
+ .distinctUntilChanged()
+
+ private val _lastPulseLightToDark: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
+ /** Tracks whether a face iconView last pulsed light to dark (vs. dark to light) */
+ val lastPulseLightToDark: Flow<Boolean> = _lastPulseLightToDark.asStateFlow()
+
val iconSize: Flow<Pair<Int, Int>> =
combine(
promptViewModel.position,
@@ -160,22 +195,35 @@
}
}
AuthType.Face ->
- combine(
- promptViewModel.isAuthenticated.distinctUntilChanged(),
- promptViewModel.isAuthenticating.distinctUntilChanged(),
- promptViewModel.isPendingConfirmation.distinctUntilChanged(),
- promptViewModel.showingError.distinctUntilChanged()
- ) {
- authState: PromptAuthState,
- isAuthenticating: Boolean,
- isPendingConfirmation: Boolean,
- showingError: Boolean ->
- getFaceIconViewAsset(
- authState,
- isAuthenticating,
- isPendingConfirmation,
- showingError
- )
+ shouldPulseAnimation.flatMapLatest { shouldPulseAnimation: Boolean ->
+ if (shouldPulseAnimation) {
+ val iconAsset =
+ if (_lastPulseLightToDark.value) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ _lastPulseLightToDark.value = !_lastPulseLightToDark.value
+ flowOf(iconAsset)
+ } else {
+ combine(
+ promptViewModel.isAuthenticated.distinctUntilChanged(),
+ promptViewModel.isAuthenticating.distinctUntilChanged(),
+ promptViewModel.isPendingConfirmation.distinctUntilChanged(),
+ promptViewModel.showingError.distinctUntilChanged()
+ ) {
+ authState: PromptAuthState,
+ isAuthenticating: Boolean,
+ isPendingConfirmation: Boolean,
+ showingError: Boolean ->
+ getFaceIconViewAsset(
+ authState,
+ isAuthenticating,
+ isPendingConfirmation,
+ showingError
+ )
+ }
+ }
}
AuthType.Coex ->
combine(
@@ -279,7 +327,8 @@
} else if (authState.isAuthenticated) {
R.drawable.face_dialog_dark_to_checkmark
} else if (isAuthenticating) {
- R.raw.face_dialog_authenticating
+ _lastPulseLightToDark.value = false
+ R.drawable.face_dialog_pulse_dark_to_light
} else if (showingError) {
R.drawable.face_dialog_dark_to_error
} else if (_previousIconWasError.value) {
@@ -654,6 +703,16 @@
}
}
+ /** Whether the current BiometricPromptLayout.iconView asset animation should be repeated. */
+ val shouldRepeatAnimation: Flow<Boolean> =
+ activeAuthType.flatMapLatest { activeAuthType: AuthType ->
+ when (activeAuthType) {
+ AuthType.Fingerprint,
+ AuthType.Coex -> flowOf(false)
+ AuthType.Face -> promptViewModel.isAuthenticating.map { it }
+ }
+ }
+
/** Called on configuration changes */
fun onConfigurationChanged(newConfig: Configuration) {
displayStateInteractor.onConfigurationChanged(newConfig)
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt b/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt
index 2b9fc73..7a9429e 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/dagger/ScreenBrightnessModule.kt
@@ -20,8 +20,15 @@
import com.android.systemui.brightness.data.repository.BrightnessPolicyRepositoryImpl
import com.android.systemui.brightness.data.repository.ScreenBrightnessDisplayManagerRepository
import com.android.systemui.brightness.data.repository.ScreenBrightnessRepository
+import com.android.systemui.brightness.shared.model.BrightnessLog
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.log.table.TableLogBufferFactory
import dagger.Binds
import dagger.Module
+import dagger.Provides
@Module
interface ScreenBrightnessModule {
@@ -33,4 +40,20 @@
@Binds
fun bindPolicyRepository(impl: BrightnessPolicyRepositoryImpl): BrightnessPolicyRepository
+
+ companion object {
+ @Provides
+ @SysUISingleton
+ @BrightnessLog
+ fun providesBrightnessTableLog(factory: TableLogBufferFactory): TableLogBuffer {
+ return factory.create("BrightnessTableLog", 50)
+ }
+
+ @Provides
+ @SysUISingleton
+ @BrightnessLog
+ fun providesBrightnessLog(factory: LogBufferFactory): LogBuffer {
+ return factory.create("BrightnessLog", 50)
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt
deleted file mode 100644
index 608f301..0000000
--- a/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.brightness.data.model
-
-@JvmInline
-value class LinearBrightness(val floatValue: Float) {
- fun clamp(min: LinearBrightness, max: LinearBrightness): LinearBrightness {
- return if (floatValue < min.floatValue) {
- min
- } else if (floatValue > max.floatValue) {
- max
- } else {
- this
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
index 9ed11d1..37d1887 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt
@@ -19,12 +19,18 @@
import android.annotation.SuppressLint
import android.hardware.display.BrightnessInfo
import android.hardware.display.DisplayManager
-import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.brightness.shared.model.BrightnessLog
+import com.android.systemui.brightness.shared.model.LinearBrightness
+import com.android.systemui.brightness.shared.model.formatBrightness
+import com.android.systemui.brightness.shared.model.logDiffForTable
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
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.DisplayId
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.table.TableLogBuffer
import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
@@ -32,13 +38,13 @@
import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@@ -78,6 +84,8 @@
constructor(
@DisplayId private val displayId: Int,
private val displayManager: DisplayManager,
+ @BrightnessLog private val logBuffer: LogBuffer,
+ @BrightnessLog private val tableBuffer: TableLogBuffer,
@Application private val applicationScope: CoroutineScope,
@Background private val backgroundContext: CoroutineContext,
) : ScreenBrightnessRepository {
@@ -100,6 +108,7 @@
displayManager.setBrightness(displayId, value)
}
}
+ logBrightnessChange(call is SetBrightnessMethod.Permanent, value)
}
}
}
@@ -147,13 +156,15 @@
brightnessInfo
.filterNotNull()
.map { LinearBrightness(it.brightnessMinimum) }
- .shareIn(applicationScope, SharingStarted.WhileSubscribed())
+ .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_MIN, null)
+ .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f))
- override val maxLinearBrightness =
+ override val maxLinearBrightness: SharedFlow<LinearBrightness> =
brightnessInfo
.filterNotNull()
.map { LinearBrightness(it.brightnessMaximum) }
- .shareIn(applicationScope, SharingStarted.WhileSubscribed())
+ .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_MAX, null)
+ .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(1f))
override suspend fun getMinMaxLinearBrightness(): Pair<LinearBrightness, LinearBrightness> {
val brightnessInfo = brightnessInfo.value ?: brightnessInfoValue()
@@ -166,7 +177,8 @@
brightnessInfo
.filterNotNull()
.map { LinearBrightness(it.brightness) }
- .shareIn(applicationScope, SharingStarted.WhileSubscribed())
+ .logDiffForTable(tableBuffer, TABLE_PREFIX_LINEAR, TABLE_COLUMN_BRIGHTNESS, null)
+ .stateIn(applicationScope, SharingStarted.WhileSubscribed(), LinearBrightness(0f))
override fun setTemporaryBrightness(value: LinearBrightness) {
apiQueue.trySend(SetBrightnessMethod.Temporary(value))
@@ -183,4 +195,21 @@
@JvmInline
value class Permanent(override val value: LinearBrightness) : SetBrightnessMethod
}
+
+ private fun logBrightnessChange(permanent: Boolean, value: Float) {
+ logBuffer.log(
+ LOG_BUFFER_BRIGHTNESS_CHANGE_TAG,
+ if (permanent) LogLevel.DEBUG else LogLevel.VERBOSE,
+ { str1 = value.formatBrightness() },
+ { "Change requested: $str1" }
+ )
+ }
+
+ private companion object {
+ const val TABLE_COLUMN_BRIGHTNESS = "brightness"
+ const val TABLE_COLUMN_MIN = "min"
+ const val TABLE_COLUMN_MAX = "max"
+ const val TABLE_PREFIX_LINEAR = "linear"
+ const val LOG_BUFFER_BRIGHTNESS_CHANGE_TAG = "BrightnessChange"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt
index 799a0a1..5647f521 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractor.kt
@@ -17,12 +17,20 @@
package com.android.systemui.brightness.domain.interactor
import com.android.settingslib.display.BrightnessUtils
-import com.android.systemui.brightness.data.model.LinearBrightness
import com.android.systemui.brightness.data.repository.ScreenBrightnessRepository
-import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.brightness.shared.model.BrightnessLog
+import com.android.systemui.brightness.shared.model.GammaBrightness
+import com.android.systemui.brightness.shared.model.LinearBrightness
+import com.android.systemui.brightness.shared.model.logDiffForTable
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.log.table.TableLogBuffer
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.stateIn
/**
* Converts between [GammaBrightness] and [LinearBrightness].
@@ -34,6 +42,8 @@
@Inject
constructor(
private val screenBrightnessRepository: ScreenBrightnessRepository,
+ @Application private val applicationScope: CoroutineScope,
+ @BrightnessLog private val tableBuffer: TableLogBuffer,
) {
/** Maximum value in the Gamma space for brightness */
val maxGammaBrightness = GammaBrightness(BrightnessUtils.GAMMA_SPACE_MAX)
@@ -45,15 +55,17 @@
* Brightness in the Gamma space for the current display. It will always represent a value
* between [minGammaBrightness] and [maxGammaBrightness]
*/
- val gammaBrightness =
+ val gammaBrightness: Flow<GammaBrightness> =
with(screenBrightnessRepository) {
combine(
- linearBrightness,
- minLinearBrightness,
- maxLinearBrightness,
- ) { brightness, min, max ->
- brightness.toGammaBrightness(min, max)
- }
+ linearBrightness,
+ minLinearBrightness,
+ maxLinearBrightness,
+ ) { brightness, min, max ->
+ brightness.toGammaBrightness(min, max)
+ }
+ .logDiffForTable(tableBuffer, TABLE_PREFIX_GAMMA, TABLE_COLUMN_BRIGHTNESS, null)
+ .stateIn(applicationScope, SharingStarted.WhileSubscribed(), GammaBrightness(0))
}
/** Sets the brightness temporarily, while the user is changing it. */
@@ -91,4 +103,9 @@
BrightnessUtils.convertLinearToGammaFloat(floatValue, min.floatValue, max.floatValue)
)
}
+
+ private companion object {
+ const val TABLE_COLUMN_BRIGHTNESS = "brightness"
+ const val TABLE_PREFIX_GAMMA = "gamma"
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt
deleted file mode 100644
index e20d003..0000000
--- a/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.brightness.shared
-
-import androidx.annotation.IntRange
-import com.android.settingslib.display.BrightnessUtils
-
-@JvmInline
-value class GammaBrightness(
- @IntRange(
- from = BrightnessUtils.GAMMA_SPACE_MIN.toLong(),
- to = BrightnessUtils.GAMMA_SPACE_MAX.toLong()
- )
- val value: Int
-)
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/BrightnessLog.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
copy to packages/SystemUI/src/com/android/systemui/brightness/shared/model/BrightnessLog.kt
index 0854e93..b514fef 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/BrightnessLog.kt
@@ -13,14 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.dreams.homecontrols
-import android.app.Activity
-import android.service.dreams.DreamService
-import javax.inject.Inject
+package com.android.systemui.brightness.shared.model
-class DreamActivityProviderImpl @Inject constructor() : DreamActivityProvider {
- override fun getActivity(dreamService: DreamService): Activity {
- return dreamService.activity
- }
-}
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class BrightnessLog()
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/model/GammaBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/GammaBrightness.kt
new file mode 100644
index 0000000..7eba626
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/GammaBrightness.kt
@@ -0,0 +1,50 @@
+/*
+ * 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.brightness.shared.model
+
+import androidx.annotation.IntRange
+import com.android.settingslib.display.BrightnessUtils
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlinx.coroutines.flow.Flow
+
+@JvmInline
+value class GammaBrightness(
+ @IntRange(
+ from = BrightnessUtils.GAMMA_SPACE_MIN.toLong(),
+ to = BrightnessUtils.GAMMA_SPACE_MAX.toLong()
+ )
+ val value: Int
+)
+
+internal fun Flow<GammaBrightness>.logDiffForTable(
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String,
+ columnName: String,
+ initialValue: GammaBrightness?,
+): Flow<GammaBrightness> {
+ val initialValueFun = {
+ tableLogBuffer.logChange(columnPrefix, columnName, initialValue?.value, isInitial = true)
+ initialValue
+ }
+ return this.pairwiseBy(initialValueFun) { prevVal: GammaBrightness?, newVal: GammaBrightness ->
+ if (prevVal != newVal) {
+ tableLogBuffer.logChange(columnPrefix, columnName, newVal.value)
+ }
+ newVal
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/shared/model/LinearBrightness.kt b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/LinearBrightness.kt
new file mode 100644
index 0000000..1c886e6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/LinearBrightness.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.brightness.shared.model
+
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.util.kotlin.pairwiseBy
+import kotlinx.coroutines.flow.Flow
+
+@JvmInline
+value class LinearBrightness(val floatValue: Float) {
+ fun clamp(min: LinearBrightness, max: LinearBrightness): LinearBrightness {
+ return if (floatValue < min.floatValue) {
+ min
+ } else if (floatValue > max.floatValue) {
+ max
+ } else {
+ this
+ }
+ }
+
+ val loggableString: String
+ get() = floatValue.formatBrightness()
+}
+
+fun Float.formatBrightness(): String {
+ return "%.3f".format(this)
+}
+
+internal fun Flow<LinearBrightness>.logDiffForTable(
+ tableLogBuffer: TableLogBuffer,
+ columnPrefix: String,
+ columnName: String,
+ initialValue: LinearBrightness?,
+): Flow<LinearBrightness> {
+ val initialValueFun = {
+ tableLogBuffer.logChange(
+ columnPrefix,
+ columnName,
+ initialValue?.loggableString,
+ isInitial = true
+ )
+ initialValue
+ }
+ return this.pairwiseBy(initialValueFun) { prevVal: LinearBrightness?, newVal: LinearBrightness
+ ->
+ if (prevVal != newVal) {
+ tableLogBuffer.logChange(columnPrefix, columnName, newVal.loggableString)
+ }
+ newVal
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
index a51d8ff..f991d5b 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/compose/BrightnessSlider.kt
@@ -33,14 +33,13 @@
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.android.compose.PlatformSlider
-import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.brightness.shared.model.GammaBrightness
import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel
import com.android.systemui.brightness.ui.viewmodel.Drag
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.common.ui.compose.Icon
import com.android.systemui.utils.PolicyRestriction
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
@Composable
@@ -107,8 +106,8 @@
viewModel: BrightnessSliderViewModel,
modifier: Modifier = Modifier,
) {
- val gamma: Int by
- viewModel.currentBrightness.map { it.value }.collectAsStateWithLifecycle(initialValue = 0)
+ val state by viewModel.currentBrightness.collectAsStateWithLifecycle()
+ val gamma = state.value
val coroutineScope = rememberCoroutineScope()
val restriction by
viewModel.policyRestriction.collectAsStateWithLifecycle(
diff --git a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
index f0988ba..16a1dcc 100644
--- a/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModel.kt
@@ -18,14 +18,18 @@
import com.android.systemui.brightness.domain.interactor.BrightnessPolicyEnforcementInteractor
import com.android.systemui.brightness.domain.interactor.ScreenBrightnessInteractor
-import com.android.systemui.brightness.shared.GammaBrightness
+import com.android.systemui.brightness.shared.model.GammaBrightness
import com.android.systemui.common.shared.model.ContentDescription
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.common.shared.model.Text
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.res.R
import com.android.systemui.utils.PolicyRestriction
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.stateIn
@SysUISingleton
class BrightnessSliderViewModel
@@ -33,8 +37,14 @@
constructor(
private val screenBrightnessInteractor: ScreenBrightnessInteractor,
private val brightnessPolicyEnforcementInteractor: BrightnessPolicyEnforcementInteractor,
+ @Application private val applicationScope: CoroutineScope,
) {
- val currentBrightness = screenBrightnessInteractor.gammaBrightness
+ val currentBrightness =
+ screenBrightnessInteractor.gammaBrightness.stateIn(
+ applicationScope,
+ SharingStarted.WhileSubscribed(),
+ GammaBrightness(0)
+ )
val maxBrightness = screenBrightnessInteractor.maxGammaBrightness
val minBrightness = screenBrightnessInteractor.minGammaBrightness
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
index 153b7aa..8993a3b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalDreamStartable.kt
@@ -19,6 +19,7 @@
import android.annotation.SuppressLint
import android.app.DreamManager
import com.android.systemui.CoreStartable
+import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming
import com.android.systemui.Flags.communalHub
import com.android.systemui.Flags.restartDreamOnUnocclude
import com.android.systemui.communal.domain.interactor.CommunalInteractor
@@ -30,11 +31,11 @@
import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.kotlin.sample
-import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+import javax.inject.Inject
/**
* A [CoreStartable] responsible for automatically starting the dream when the communal hub is
@@ -78,6 +79,7 @@
if (
finishedState == KeyguardState.GLANCEABLE_HUB &&
!dreaming &&
+ !glanceableHubAllowKeyguardWhenDreaming() &&
dreamManager.canStartDreaming(isAwake)
) {
dreamManager.startDream()
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index 3d9e861..8cd5603 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
@@ -91,6 +91,12 @@
/** A list of all the communal content to be displayed in the communal hub. */
abstract val communalContent: Flow<List<CommunalContentModel>>
+ /**
+ * Whether to freeze the emission of the communalContent flow to prevent recomposition. Defaults
+ * to false, indicating that the flow will emit new update.
+ */
+ open val isCommunalContentFlowFrozen: Flow<Boolean> = flowOf(false)
+
/** Whether in edit mode for the communal hub. */
open val isEditMode = false
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
index 7f3a2dc..ce69ee8 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalViewModel.kt
@@ -38,7 +38,9 @@
import com.android.systemui.media.dagger.MediaModule
import com.android.systemui.res.R
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
import javax.inject.Inject
import javax.inject.Named
import kotlinx.coroutines.CoroutineScope
@@ -76,8 +78,11 @@
private val logger = Logger(logBuffer, "CommunalViewModel")
+ /** Communal content saved from the previous emission when the flow is active (not "frozen"). */
+ private var frozenCommunalContent: List<CommunalContentModel>? = null
+
@OptIn(ExperimentalCoroutinesApi::class)
- override val communalContent: Flow<List<CommunalContentModel>> =
+ private val latestCommunalContent: Flow<List<CommunalContentModel>> =
tutorialInteractor.isTutorialAvailable
.flatMapLatest { isTutorialMode ->
if (isTutorialMode) {
@@ -93,9 +98,40 @@
}
}
.onEach { models ->
+ frozenCommunalContent = models
logger.d({ "Content updated: $str1" }) { str1 = models.joinToString { it.key } }
}
+ /**
+ * Freeze the content flow, when an activity is about to show, like starting a timer via voice:
+ * 1) in handheld mode, use the keyguard occluded state;
+ * 2) in dreaming mode, where keyguard is already occluded by dream, use the dream wakeup
+ * signal. Since in this case the shell transition info does not include
+ * KEYGUARD_VISIBILITY_TRANSIT_FLAGS, KeyguardTransitionHandler will not run the
+ * occludeAnimation on KeyguardViewMediator.
+ */
+ override val isCommunalContentFlowFrozen: Flow<Boolean> =
+ allOf(
+ keyguardTransitionInteractor.isFinishedInState(KeyguardState.GLANCEABLE_HUB),
+ keyguardInteractor.isKeyguardOccluded,
+ not(keyguardInteractor.isAbleToDream)
+ )
+ .distinctUntilChanged()
+ .onEach { logger.d("isCommunalContentFlowFrozen: $it") }
+
+ override val communalContent: Flow<List<CommunalContentModel>> =
+ isCommunalContentFlowFrozen
+ .flatMapLatestConflated { isFrozen ->
+ if (isFrozen) {
+ flowOf(frozenCommunalContent ?: emptyList())
+ } else {
+ latestCommunalContent
+ }
+ }
+ .onEach { models ->
+ logger.d({ "CommunalContent: $str1" }) { str1 = models.joinToString { it.key } }
+ }
+
override val isEmptyState: Flow<Boolean> =
communalInteractor.widgetContent
.map { it.isEmpty() }
diff --git a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java
index 3cf22b1..a679bfb 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java
@@ -67,7 +67,7 @@
@Override
public int getRequiredTypeAvailability() {
// TODO(b/339667383): create a new complication type if we decide to productionize this
- return COMPLICATION_TYPE_HOME_CONTROLS;
+ return COMPLICATION_TYPE_NONE;
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt b/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt
index 4dbb32d..1bbdfcd 100644
--- a/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt
+++ b/packages/SystemUI/src/com/android/systemui/dock/DockManagerExtensions.kt
@@ -19,16 +19,18 @@
import com.android.systemui.common.coroutine.ConflatedCallbackFlow
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
/**
- * Retrieves whether or not the device is docked according to DockManager. Emits a starting value
- * of isDocked.
+ * Retrieves whether or not the device is docked according to DockManager. Emits a starting value of
+ * isDocked.
*/
fun DockManager.retrieveIsDocked(): Flow<Boolean> =
ConflatedCallbackFlow.conflatedCallbackFlow {
- val callback = DockManager.DockEventListener { trySend(isDocked) }
- addListener(callback)
- trySend(isDocked)
+ val callback = DockManager.DockEventListener { trySend(isDocked) }
+ addListener(callback)
+ trySend(isDocked)
- awaitClose { removeListener(callback) }
- }
\ No newline at end of file
+ awaitClose { removeListener(callback) }
+ }
+ .distinctUntilChanged()
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 96e708f..c6c57479 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -18,6 +18,7 @@
import static android.service.dreams.Flags.dreamWakeRedirect;
+import static com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming;
import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_WINDOW_TITLE;
import static com.android.systemui.dreams.dagger.DreamModule.DREAM_TOUCH_INSET_MANAGER;
import static com.android.systemui.dreams.dagger.DreamModule.HOME_CONTROL_PANEL_DREAM_COMPONENT;
@@ -395,7 +396,7 @@
return;
}
- redirectWake(mCommunalAvailable);
+ redirectWake(mCommunalAvailable && !glanceableHubAllowKeyguardWhenDreaming());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index b0d134f..f6ac7a5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -33,8 +33,8 @@
import com.android.systemui.dreams.DreamOverlayService;
import com.android.systemui.dreams.SystemDialogsCloser;
import com.android.systemui.dreams.complication.dagger.ComplicationComponent;
-import com.android.systemui.dreams.homecontrols.DreamActivityProvider;
-import com.android.systemui.dreams.homecontrols.DreamActivityProviderImpl;
+import com.android.systemui.dreams.homecontrols.DreamServiceDelegate;
+import com.android.systemui.dreams.homecontrols.DreamServiceDelegateImpl;
import com.android.systemui.dreams.homecontrols.HomeControlsDreamService;
import com.android.systemui.qs.QsEventLogger;
import com.android.systemui.qs.pipeline.shared.TileSpec;
@@ -202,8 +202,8 @@
}
- /** Provides activity for dream service */
+ /** Provides delegate to allow for testing of dream service */
@Binds
- DreamActivityProvider bindActivityProvider(DreamActivityProviderImpl impl);
+ DreamServiceDelegate bindDreamDelegate(DreamServiceDelegateImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt
deleted file mode 100644
index b35b7f5..0000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.dreams.homecontrols
-
-import android.app.Activity
-import android.service.dreams.DreamService
-
-fun interface DreamActivityProvider {
- /**
- * Provides abstraction for getting the activity associated with a dream service, so that the
- * activity can be mocked in tests.
- */
- fun getActivity(dreamService: DreamService): Activity?
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt
new file mode 100644
index 0000000..2cfb02e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.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.dreams.homecontrols
+
+import android.app.Activity
+import android.service.dreams.DreamService
+
+/** Provides abstraction for [DreamService] methods, so they can be mocked in tests. */
+interface DreamServiceDelegate {
+ /** Wrapper for [DreamService.getActivity] which can be mocked in tests. */
+ fun getActivity(dreamService: DreamService): Activity?
+
+ /** Wrapper for [DreamService.wakeUp] which can be mocked in tests. */
+ fun wakeUp(dreamService: DreamService)
+
+ /** Wrapper for [DreamService.finish] which can be mocked in tests. */
+ fun finish(dreamService: DreamService)
+
+ /** Wrapper for [DreamService.getRedirectWake] which can be mocked in tests. */
+ fun redirectWake(dreamService: DreamService): Boolean
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt
similarity index 69%
rename from packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
rename to packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt
index 0854e93..7dc5434 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegateImpl.kt
@@ -19,8 +19,20 @@
import android.service.dreams.DreamService
import javax.inject.Inject
-class DreamActivityProviderImpl @Inject constructor() : DreamActivityProvider {
+class DreamServiceDelegateImpl @Inject constructor() : DreamServiceDelegate {
override fun getActivity(dreamService: DreamService): Activity {
return dreamService.activity
}
+
+ override fun finish(dreamService: DreamService) {
+ dreamService.finish()
+ }
+
+ override fun wakeUp(dreamService: DreamService) {
+ dreamService.wakeUp()
+ }
+
+ override fun redirectWake(dreamService: DreamService): Boolean {
+ return dreamService.redirectWake
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
index 76187c6..77c54ec 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt
@@ -31,6 +31,7 @@
import com.android.systemui.util.wakelock.WakeLock
import com.android.systemui.util.wakelock.WakeLock.Builder.NO_TIMEOUT
import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -46,7 +47,7 @@
private val taskFragmentFactory: TaskFragmentComponent.Factory,
private val homeControlsComponentInteractor: HomeControlsComponentInteractor,
private val wakeLockBuilder: WakeLock.Builder,
- private val dreamActivityProvider: DreamActivityProvider,
+ private val dreamServiceDelegate: DreamServiceDelegate,
@Background private val bgDispatcher: CoroutineDispatcher,
@DreamLog logBuffer: LogBuffer
) : DreamService() {
@@ -65,7 +66,7 @@
override fun onAttachedToWindow() {
super.onAttachedToWindow()
- val activity = dreamActivityProvider.getActivity(this)
+ val activity = dreamServiceDelegate.getActivity(this)
if (activity == null) {
finish()
return
@@ -79,9 +80,9 @@
taskFragmentFactory
.create(
activity = activity,
- onCreateCallback = this::onTaskFragmentCreated,
+ onCreateCallback = { launchActivity() },
onInfoChangedCallback = this::onTaskFragmentInfoChanged,
- hide = { endDream() }
+ hide = { endDream(false) }
)
.apply { createTaskFragment() }
@@ -91,16 +92,24 @@
private fun onTaskFragmentInfoChanged(taskFragmentInfo: TaskFragmentInfo) {
if (taskFragmentInfo.isEmpty) {
logger.d("Finishing dream due to TaskFragment being empty")
- endDream()
+ endDream(true)
}
}
- private fun endDream() {
+ private fun endDream(handleRedirect: Boolean) {
homeControlsComponentInteractor.onDreamEndUnexpectedly()
- finish()
+ if (handleRedirect && dreamServiceDelegate.redirectWake(this)) {
+ dreamServiceDelegate.wakeUp(this)
+ serviceScope.launch {
+ delay(ACTIVITY_RESTART_DELAY)
+ launchActivity()
+ }
+ } else {
+ dreamServiceDelegate.finish(this)
+ }
}
- private fun onTaskFragmentCreated(taskFragmentInfo: TaskFragmentInfo) {
+ private fun launchActivity() {
val setting = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
val componentName = homeControlsComponentInteractor.panelComponent.value
logger.d("Starting embedding $componentName")
@@ -134,6 +143,14 @@
* complete.
*/
val CANCELLATION_DELAY_AFTER_DETACHED = 5.seconds
+
+ /**
+ * Defines the delay after wakeup where we should attempt to restart the embedded activity.
+ * When a wakeup is redirected, the dream service may keep running. In this case, we should
+ * restart the activity if it finished. This delays ensures the activity is only restarted
+ * after the wakeup transition has played.
+ */
+ val ACTIVITY_RESTART_DELAY = 334.milliseconds
const val TAG = "HomeControlsDreamService"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index c5b3c53..4b07f78 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -17,6 +17,7 @@
package com.android.systemui.dreams.ui.viewmodel
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming
import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
import com.android.systemui.communal.domain.interactor.CommunalInteractor
import com.android.systemui.communal.shared.model.CommunalScenes
@@ -60,7 +61,7 @@
val showGlanceableHub =
communalInteractor.isCommunalEnabled.value &&
!keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
- if (showGlanceableHub) {
+ if (showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()) {
communalInteractor.changeScene(CommunalScenes.Communal)
} else {
toLockscreenTransitionViewModel.startTransition()
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index c084340..3d3584e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -60,14 +60,6 @@
"notification_drag_to_contents"
)
- /**
- * This flag controls whether we register a listener for StatsD notification memory reports.
- * For statsd to actually call the listener however, a server-side toggle needs to be
- * enabled as well.
- */
- val NOTIFICATION_MEMORY_LOGGING_ENABLED =
- releasedFlag("notification_memory_logging_enabled")
-
// TODO(b/280783617): Tracking Bug
@Keep
@JvmField
@@ -386,9 +378,6 @@
val WARN_ON_BLOCKING_BINDER_TRANSACTIONS =
unreleasedFlag("warn_on_blocking_binder_transactions")
- // TODO:(b/283203305): Tracking bug
- @JvmField val TRIM_FONT_CACHES_AT_UNLOCK = unreleasedFlag("trim_font_caches_on_unlock")
-
// TODO(b/298380520): Tracking Bug
@JvmField
val USER_TRACKER_BACKGROUND_CALLBACKS = unreleasedFlag("user_tracker_background_callbacks")
@@ -459,14 +448,6 @@
@JvmField
val ENABLE_CLOCK_KEYGUARD_PRESENTATION = releasedFlag("enable_clock_keyguard_presentation")
- /** Enable the Compose implementation of the PeopleSpaceActivity. */
- @JvmField
- val COMPOSE_PEOPLE_SPACE = releasedFlag("compose_people_space")
-
- /** Enable the Compose implementation of the Quick Settings footer actions. */
- @JvmField
- val COMPOSE_QS_FOOTER_ACTIONS = releasedFlag("compose_qs_footer_actions")
-
/** Enable the share wifi button in Quick Settings internet dialog. */
@JvmField
val SHARE_WIFI_QS_BUTTON = releasedFlag("share_wifi_qs_button")
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
index ea8d7d7..30b9583 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffect.kt
@@ -19,7 +19,9 @@
import android.os.VibrationEffect
import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.systemui.animation.Expandable
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.statusbar.VibratorHelper
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
@@ -51,6 +53,10 @@
var state = State.IDLE
private set
+ /** The QSTile and Expandable used to perform a long-click and click actions */
+ var qsTile: QSTile? = null
+ var expandable: Expandable? = null
+
/** Flow for view control and action */
private val _postedActionType = MutableStateFlow<ActionType?>(null)
val actionType: Flow<ActionType?> =
@@ -105,6 +111,7 @@
when (state) {
State.IDLE -> {
setState(State.TIMEOUT_WAIT)
+ _postedActionType.value = ActionType.WAIT_TAP_TIMEOUT
}
State.RUNNING_BACKWARDS -> _postedActionType.value = ActionType.CANCEL_ANIMATOR
else -> {}
@@ -112,16 +119,9 @@
}
fun handleActionUp() {
- when (state) {
- State.TIMEOUT_WAIT -> {
- _postedActionType.value = ActionType.CLICK
- setState(State.IDLE)
- }
- State.RUNNING_FORWARD -> {
- _postedActionType.value = ActionType.REVERSE_ANIMATOR
- setState(State.RUNNING_BACKWARDS)
- }
- else -> {}
+ if (state == State.RUNNING_FORWARD) {
+ _postedActionType.value = ActionType.REVERSE_ANIMATOR
+ setState(State.RUNNING_BACKWARDS)
}
}
@@ -129,6 +129,7 @@
when (state) {
State.TIMEOUT_WAIT -> {
setState(State.IDLE)
+ clearActionType()
}
State.RUNNING_FORWARD -> {
_postedActionType.value = ActionType.REVERSE_ANIMATOR
@@ -145,18 +146,23 @@
/** This function is called both when an animator completes or gets cancelled */
fun handleAnimationComplete() {
- if (state == State.RUNNING_FORWARD) {
- vibrate(snapEffect)
- _postedActionType.value = ActionType.LONG_PRESS
- }
- if (state != State.TIMEOUT_WAIT) {
- // This will happen if the animator did not finish by being cancelled
- setState(State.IDLE)
+ when (state) {
+ State.RUNNING_FORWARD -> {
+ setState(State.IDLE)
+ vibrate(snapEffect)
+ _postedActionType.value = ActionType.LONG_PRESS
+ }
+ State.RUNNING_BACKWARDS -> {
+ setState(State.IDLE)
+ clearActionType()
+ }
+ else -> {}
}
}
fun handleAnimationCancel() {
setState(State.TIMEOUT_WAIT)
+ _postedActionType.value = ActionType.WAIT_TAP_TIMEOUT
}
fun handleTimeoutComplete() {
@@ -190,9 +196,22 @@
effectDuration
)
setState(State.IDLE)
+ clearActionType()
return true
}
+ fun onTileClick(): Boolean {
+ if (state == State.TIMEOUT_WAIT) {
+ setState(State.IDLE)
+ clearActionType()
+ qsTile?.let {
+ it.click(expandable)
+ return true
+ }
+ }
+ return false
+ }
+
enum class State {
IDLE, /* The effect is idle waiting for touch input */
TIMEOUT_WAIT, /* The effect is waiting for a [PRESSED_TIMEOUT] period */
@@ -202,7 +221,7 @@
/* A type of action to perform on the view depending on the effect's state and logic */
enum class ActionType {
- CLICK,
+ WAIT_TAP_TIMEOUT,
LONG_PRESS,
RESET_AND_LONG_PRESS,
START_ANIMATOR,
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
index 4875f48..92a55ef 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/qs/QSLongPressEffectViewBinder.kt
@@ -17,8 +17,6 @@
package com.android.systemui.haptics.qs
import android.animation.ValueAnimator
-import android.annotation.SuppressLint
-import android.view.MotionEvent
import android.view.ViewConfiguration
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.core.animation.doOnCancel
@@ -30,6 +28,7 @@
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.qs.tileimpl.QSTileViewImpl
import kotlinx.coroutines.DisposableHandle
+import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.filterNotNull
object QSLongPressEffectViewBinder {
@@ -41,9 +40,6 @@
): DisposableHandle? {
if (qsLongPressEffect == null) return null
- // Set the touch listener as the long-press effect
- setTouchListener(tile, qsLongPressEffect)
-
return tile.repeatWhenAttached {
repeatOnLifecycle(Lifecycle.State.CREATED) {
// Action to perform
@@ -52,18 +48,18 @@
qsLongPressEffect.actionType.filterNotNull().collect { action ->
when (action) {
- QSLongPressEffect.ActionType.CLICK -> {
- tile.performClick()
- qsLongPressEffect.clearActionType()
+ QSLongPressEffect.ActionType.WAIT_TAP_TIMEOUT -> {
+ delay(ViewConfiguration.getTapTimeout().toLong())
+ qsLongPressEffect.handleTimeoutComplete()
}
QSLongPressEffect.ActionType.LONG_PRESS -> {
tile.prepareForLaunch()
- tile.performLongClick()
+ qsLongPressEffect.qsTile?.longClick(qsLongPressEffect.expandable)
qsLongPressEffect.clearActionType()
}
QSLongPressEffect.ActionType.RESET_AND_LONG_PRESS -> {
tile.resetLongPressEffectProperties()
- tile.performLongClick()
+ qsLongPressEffect.qsTile?.longClick(qsLongPressEffect.expandable)
qsLongPressEffect.clearActionType()
}
QSLongPressEffect.ActionType.START_ANIMATOR -> {
@@ -106,22 +102,4 @@
}
}
}
-
- @SuppressLint("ClickableViewAccessibility")
- private fun setTouchListener(tile: QSTileViewImpl, longPressEffect: QSLongPressEffect?) {
- tile.setOnTouchListener { _, event ->
- when (event.actionMasked) {
- MotionEvent.ACTION_DOWN -> {
- tile.postDelayed(
- { longPressEffect?.handleTimeoutComplete() },
- ViewConfiguration.getTapTimeout().toLong(),
- )
- longPressEffect?.handleActionDown()
- }
- MotionEvent.ACTION_UP -> longPressEffect?.handleActionUp()
- MotionEvent.ACTION_CANCEL -> longPressEffect?.handleActionCancel()
- }
- true
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
new file mode 100644
index 0000000..52ccc21
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/composable/ShortcutHelper.kt
@@ -0,0 +1,413 @@
+/*
+ * 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.keyboard.shortcut.ui.composable
+
+import androidx.annotation.StringRes
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.heightIn
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.automirrored.filled.OpenInNew
+import androidx.compose.material.icons.filled.Accessibility
+import androidx.compose.material.icons.filled.Apps
+import androidx.compose.material.icons.filled.ExpandMore
+import androidx.compose.material.icons.filled.Keyboard
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.material.icons.filled.Tv
+import androidx.compose.material.icons.filled.VerticalSplit
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.NavigationDrawerItemColors
+import androidx.compose.material3.NavigationDrawerItemDefaults
+import androidx.compose.material3.SearchBar
+import androidx.compose.material3.SearchBarDefaults
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.util.fastForEach
+import androidx.compose.ui.util.fastForEachIndexed
+import com.android.compose.windowsizeclass.LocalWindowSizeClass
+import com.android.systemui.res.R
+
+@Composable
+fun ShortcutHelper(modifier: Modifier = Modifier, onKeyboardSettingsClicked: () -> Unit) {
+ if (shouldUseSinglePane()) {
+ ShortcutHelperSinglePane(modifier, categories, onKeyboardSettingsClicked)
+ } else {
+ ShortcutHelperTwoPane(modifier, categories, onKeyboardSettingsClicked)
+ }
+}
+
+@Composable
+private fun shouldUseSinglePane() =
+ LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact
+
+@Composable
+private fun ShortcutHelperSinglePane(
+ modifier: Modifier = Modifier,
+ categories: List<ShortcutHelperCategory>,
+ onKeyboardSettingsClicked: () -> Unit,
+) {
+ Column(
+ modifier =
+ modifier
+ .fillMaxSize()
+ .verticalScroll(rememberScrollState())
+ .padding(start = 16.dp, end = 16.dp, top = 26.dp)
+ ) {
+ TitleBar()
+ Spacer(modifier = Modifier.height(6.dp))
+ ShortcutsSearchBar()
+ Spacer(modifier = Modifier.height(16.dp))
+ CategoriesPanelSinglePane(categories)
+ Spacer(modifier = Modifier.weight(1f))
+ KeyboardSettings(onClick = onKeyboardSettingsClicked)
+ }
+}
+
+@Composable
+private fun CategoriesPanelSinglePane(
+ categories: List<ShortcutHelperCategory>,
+) {
+ var expandedCategory by remember { mutableStateOf<ShortcutHelperCategory?>(null) }
+ Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
+ categories.fastForEachIndexed { index, category ->
+ val isExpanded = expandedCategory == category
+ val itemShape =
+ if (index == 0) {
+ ShortcutHelper.Shapes.singlePaneFirstCategory
+ } else if (index == categories.lastIndex) {
+ ShortcutHelper.Shapes.singlePaneLastCategory
+ } else {
+ ShortcutHelper.Shapes.singlePaneCategory
+ }
+ CategoryItemSinglePane(
+ category = category,
+ isExpanded = isExpanded,
+ onClick = {
+ expandedCategory =
+ if (isExpanded) {
+ null
+ } else {
+ category
+ }
+ },
+ shape = itemShape,
+ )
+ }
+ }
+}
+
+@Composable
+private fun CategoryItemSinglePane(
+ category: ShortcutHelperCategory,
+ isExpanded: Boolean,
+ onClick: () -> Unit,
+ shape: Shape,
+) {
+ Surface(
+ color = MaterialTheme.colorScheme.surfaceBright,
+ shape = shape,
+ onClick = onClick,
+ ) {
+ Column {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier = Modifier.fillMaxWidth().heightIn(min = 88.dp).padding(horizontal = 16.dp)
+ ) {
+ Icon(category.icon, contentDescription = null)
+ Spacer(modifier = Modifier.width(16.dp))
+ Text(stringResource(category.labelResId))
+ Spacer(modifier = Modifier.weight(1f))
+ RotatingExpandCollapseIcon(isExpanded)
+ }
+ AnimatedVisibility(visible = isExpanded) { ShortcutCategoryDetailsSinglePane(category) }
+ }
+ }
+}
+
+@Composable
+private fun RotatingExpandCollapseIcon(isExpanded: Boolean) {
+ val expandIconRotationDegrees by
+ animateFloatAsState(
+ targetValue =
+ if (isExpanded) {
+ 180f
+ } else {
+ 0f
+ },
+ label = "Expand icon rotation animation"
+ )
+ Icon(
+ modifier =
+ Modifier.background(
+ color = MaterialTheme.colorScheme.surfaceContainerHigh,
+ shape = CircleShape
+ )
+ .graphicsLayer { rotationZ = expandIconRotationDegrees },
+ imageVector = Icons.Default.ExpandMore,
+ contentDescription =
+ if (isExpanded) {
+ stringResource(R.string.shortcut_helper_content_description_collapse_icon)
+ } else {
+ stringResource(R.string.shortcut_helper_content_description_expand_icon)
+ },
+ tint = MaterialTheme.colorScheme.onSurface
+ )
+}
+
+@Composable
+private fun ShortcutCategoryDetailsSinglePane(category: ShortcutHelperCategory) {
+ Box(modifier = Modifier.fillMaxWidth().heightIn(min = 300.dp)) {
+ Text(
+ modifier = Modifier.align(Alignment.Center),
+ text = stringResource(category.labelResId),
+ )
+ }
+}
+
+@Composable
+private fun ShortcutHelperTwoPane(
+ modifier: Modifier = Modifier,
+ categories: List<ShortcutHelperCategory>,
+ onKeyboardSettingsClicked: () -> Unit,
+) {
+ Column(modifier = modifier.fillMaxSize().padding(start = 24.dp, end = 24.dp, top = 26.dp)) {
+ TitleBar()
+ Spacer(modifier = Modifier.height(12.dp))
+ Row(Modifier.fillMaxWidth()) {
+ StartSidePanel(
+ modifier = Modifier.fillMaxWidth(fraction = 0.32f),
+ categories = categories,
+ onKeyboardSettingsClicked = onKeyboardSettingsClicked,
+ )
+ Spacer(modifier = Modifier.width(24.dp))
+ EndSidePanel(Modifier.fillMaxSize())
+ }
+ }
+}
+
+@Composable
+private fun StartSidePanel(
+ modifier: Modifier,
+ categories: List<ShortcutHelperCategory>,
+ onKeyboardSettingsClicked: () -> Unit,
+) {
+ Column(modifier) {
+ ShortcutsSearchBar()
+ Spacer(modifier = Modifier.heightIn(16.dp))
+ CategoriesPanelTwoPane(categories)
+ Spacer(modifier = Modifier.weight(1f))
+ KeyboardSettings(onKeyboardSettingsClicked)
+ }
+}
+
+@Composable
+private fun CategoriesPanelTwoPane(categories: List<ShortcutHelperCategory>) {
+ var selected by remember { mutableStateOf(categories.first()) }
+ Column {
+ categories.fastForEach {
+ CategoryItemTwoPane(
+ label = stringResource(it.labelResId),
+ icon = it.icon,
+ selected = selected == it,
+ onClick = { selected = it }
+ )
+ }
+ }
+}
+
+@Composable
+private fun CategoryItemTwoPane(
+ label: String,
+ icon: ImageVector,
+ selected: Boolean,
+ onClick: () -> Unit,
+ colors: NavigationDrawerItemColors =
+ NavigationDrawerItemDefaults.colors(unselectedContainerColor = Color.Transparent),
+) {
+ Surface(
+ selected = selected,
+ onClick = onClick,
+ modifier = Modifier.semantics { role = Role.Tab }.heightIn(min = 72.dp).fillMaxWidth(),
+ shape = RoundedCornerShape(28.dp),
+ color = colors.containerColor(selected).value,
+ ) {
+ Row(Modifier.padding(horizontal = 24.dp), verticalAlignment = Alignment.CenterVertically) {
+ Icon(
+ modifier = Modifier.size(24.dp),
+ imageVector = icon,
+ contentDescription = null,
+ tint = colors.iconColor(selected).value
+ )
+ Spacer(Modifier.width(12.dp))
+ Box(Modifier.weight(1f)) {
+ Text(
+ fontSize = 18.sp,
+ color = colors.textColor(selected).value,
+ style = MaterialTheme.typography.headlineSmall,
+ text = label
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun EndSidePanel(modifier: Modifier) {
+ Surface(
+ modifier = modifier,
+ shape = RoundedCornerShape(28.dp),
+ color = MaterialTheme.colorScheme.surfaceBright
+ ) {}
+}
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+private fun TitleBar() {
+ CenterAlignedTopAppBar(
+ colors = TopAppBarDefaults.centerAlignedTopAppBarColors(containerColor = Color.Transparent),
+ title = {
+ Text(
+ text = stringResource(R.string.shortcut_helper_title),
+ color = MaterialTheme.colorScheme.onSurface,
+ style = MaterialTheme.typography.headlineSmall
+ )
+ }
+ )
+}
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+private fun ShortcutsSearchBar() {
+ var query by remember { mutableStateOf("") }
+ SearchBar(
+ colors = SearchBarDefaults.colors(containerColor = MaterialTheme.colorScheme.surfaceBright),
+ query = query,
+ active = false,
+ onActiveChange = {},
+ onQueryChange = { query = it },
+ onSearch = {},
+ leadingIcon = { Icon(Icons.Default.Search, contentDescription = null) },
+ placeholder = { Text(text = stringResource(R.string.shortcut_helper_search_placeholder)) },
+ content = {}
+ )
+}
+
+@Composable
+private fun KeyboardSettings(onClick: () -> Unit) {
+ Surface(
+ onClick = onClick,
+ shape = RoundedCornerShape(24.dp),
+ color = Color.Transparent,
+ modifier = Modifier.semantics { role = Role.Button }.fillMaxWidth()
+ ) {
+ Row(
+ modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ "Keyboard Settings",
+ color = MaterialTheme.colorScheme.onSurfaceVariant,
+ fontSize = 16.sp
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Icon(
+ imageVector = Icons.AutoMirrored.Default.OpenInNew,
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onSurfaceVariant
+ )
+ }
+ }
+}
+
+/** Temporary data class just to populate the UI. */
+private data class ShortcutHelperCategory(
+ @StringRes val labelResId: Int,
+ val icon: ImageVector,
+)
+
+// Temporarily populating the categories directly in the UI.
+private val categories =
+ listOf(
+ ShortcutHelperCategory(R.string.shortcut_helper_category_system, Icons.Default.Tv),
+ ShortcutHelperCategory(
+ R.string.shortcut_helper_category_multitasking,
+ Icons.Default.VerticalSplit
+ ),
+ ShortcutHelperCategory(R.string.shortcut_helper_category_input, Icons.Default.Keyboard),
+ ShortcutHelperCategory(R.string.shortcut_helper_category_app_shortcuts, Icons.Default.Apps),
+ ShortcutHelperCategory(R.string.shortcut_helper_category_a11y, Icons.Default.Accessibility),
+ )
+
+object ShortcutHelper {
+
+ object Shapes {
+ val singlePaneFirstCategory =
+ RoundedCornerShape(
+ topStart = Dimensions.SinglePaneCategoryCornerRadius,
+ topEnd = Dimensions.SinglePaneCategoryCornerRadius
+ )
+ val singlePaneLastCategory =
+ RoundedCornerShape(
+ bottomStart = Dimensions.SinglePaneCategoryCornerRadius,
+ bottomEnd = Dimensions.SinglePaneCategoryCornerRadius
+ )
+ val singlePaneCategory = RectangleShape
+ }
+
+ object Dimensions {
+ val SinglePaneCategoryCornerRadius = 28.dp
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
index ef4156d..1e8d239 100644
--- a/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyboard/shortcut/ui/view/ShortcutHelperActivity.kt
@@ -23,9 +23,12 @@
import androidx.activity.BackEventCompat
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedCallback
+import androidx.compose.ui.platform.ComposeView
import androidx.core.view.updatePadding
import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.keyboard.shortcut.ui.composable.ShortcutHelper
import com.android.systemui.keyboard.shortcut.ui.viewmodel.ShortcutHelperViewModel
import com.android.systemui.res.R
import com.google.android.material.bottomsheet.BottomSheetBehavior
@@ -58,14 +61,30 @@
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_keyboard_shortcut_helper)
setUpBottomSheetWidth()
+ expandBottomSheet()
setUpInsets()
setUpPredictiveBack()
setUpSheetDismissListener()
setUpDismissOnTouchOutside()
+ setUpComposeView()
observeFinishRequired()
viewModel.onViewOpened()
}
+ private fun setUpComposeView() {
+ requireViewById<ComposeView>(R.id.shortcut_helper_compose_container).apply {
+ setContent {
+ PlatformTheme {
+ ShortcutHelper(
+ onKeyboardSettingsClicked = ::onKeyboardSettingsClicked,
+ )
+ }
+ }
+ }
+ }
+
+ private fun onKeyboardSettingsClicked() {}
+
override fun onDestroy() {
super.onDestroy()
if (isFinishing) {
@@ -101,7 +120,8 @@
bottomSheetContainer.setOnApplyWindowInsetsListener { _, insets ->
val safeDrawingInsets = insets.safeDrawing
// Make sure the bottom sheet is not covered by the status bar.
- bottomSheetContainer.updatePadding(top = safeDrawingInsets.top)
+ bottomSheetBehavior.maxHeight =
+ resources.displayMetrics.heightPixels - safeDrawingInsets.top
// Make sure the contents inside of the bottom sheet are not hidden by system bars, or
// cutouts.
bottomSheet.updatePadding(
@@ -171,7 +191,6 @@
private val WindowInsets.safeDrawing
get() =
getInsets(WindowInsets.Type.systemBars())
- .union(getInsets(WindowInsets.Type.ime()))
.union(getInsets(WindowInsets.Type.displayCutout()))
private fun Insets.union(insets: Insets): Insets = Insets.max(this, insets)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 81c2d92..f3a1843 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -2380,7 +2380,10 @@
}
mCustomMessage = message;
mKeyguardViewControllerLazy.get().dismissAndCollapse();
- } else if (callback != null) {
+ return;
+ }
+ Log.w(TAG, "Ignoring request to DISMISS because mShowing=false");
+ if (callback != null) {
new DismissCallbackWrapper(callback).notifyDismissError();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
index 3cbcb2c..97ea16d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ResourceTrimmer.kt
@@ -18,21 +18,15 @@
import android.annotation.WorkerThread
import android.content.ComponentCallbacks2
-import android.graphics.HardwareRenderer
-import android.os.Trace
import android.util.Log
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.Edge
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.power.domain.interactor.PowerInteractor
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.Scenes
@@ -40,10 +34,7 @@
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
/**
@@ -57,37 +48,15 @@
class ResourceTrimmer
@Inject
constructor(
- private val keyguardInteractor: KeyguardInteractor,
- private val powerInteractor: PowerInteractor,
private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
private val globalWindowManager: GlobalWindowManager,
@Application private val applicationScope: CoroutineScope,
@Background private val bgDispatcher: CoroutineDispatcher,
- private val featureFlags: FeatureFlags,
private val sceneInteractor: SceneInteractor,
-) : CoreStartable, WakefulnessLifecycle.Observer {
+) : CoreStartable {
override fun start() {
Log.d(LOG_TAG, "Resource trimmer registered.")
- if (com.android.systemui.Flags.trimResourcesWithBackgroundTrimAtLock()) {
- applicationScope.launch(bgDispatcher) {
- // We need to wait for the AoD transition (and animation) to complete.
- // This means we're waiting for isDreaming (== implies isDoze) and dozeAmount == 1f
- // signal. This is to make sure we don't clear font caches during animation which
- // would jank and leave stale data in memory.
- val isDozingFully =
- keyguardInteractor.dozeAmount.map { it == 1f }.distinctUntilChanged()
- combine(
- powerInteractor.isAsleep,
- keyguardInteractor.isDreaming,
- isDozingFully,
- ::Triple
- )
- .distinctUntilChanged()
- .collect { onWakefulnessUpdated(it.first, it.second, it.third) }
- }
- }
-
applicationScope.launch(bgDispatcher) {
// We drop 1 to avoid triggering on initial collect().
if (SceneContainerFlag.isEnabled) {
@@ -110,47 +79,9 @@
// lockscreen elements, especially clocks.
Log.d(LOG_TAG, "Sending TRIM_MEMORY_UI_HIDDEN.")
globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- if (featureFlags.isEnabled(Flags.TRIM_FONT_CACHES_AT_UNLOCK)) {
- if (DEBUG) {
- Log.d(LOG_TAG, "Trimming font caches since keyguard went away.")
- }
- globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
- }
- }
-
- @WorkerThread
- private fun onWakefulnessUpdated(
- isAsleep: Boolean,
- isDreaming: Boolean,
- isDozingFully: Boolean
- ) {
- if (!com.android.systemui.Flags.trimResourcesWithBackgroundTrimAtLock()) {
- return
- }
-
- if (DEBUG) {
- Log.d(LOG_TAG, "isAsleep: $isAsleep Dreaming: $isDreaming DozeAmount: $isDozingFully")
- }
- // There are three scenarios:
- // * No dozing and no AoD at all - where we go directly to ASLEEP with isDreaming = false
- // and dozeAmount == 0f
- // * Dozing without Aod - where we go to ASLEEP with isDreaming = true and dozeAmount jumps
- // to 1f
- // * AoD - where we go to ASLEEP with iDreaming = true and dozeAmount slowly increases
- // to 1f
- val dozeDisabledAndScreenOff = isAsleep && !isDreaming
- val dozeEnabledAndDozeAnimationCompleted = isAsleep && isDreaming && isDozingFully
- if (dozeDisabledAndScreenOff || dozeEnabledAndDozeAnimationCompleted) {
- Trace.beginSection("ResourceTrimmer#trimMemory")
- Log.d(LOG_TAG, "SysUI asleep, trimming memory.")
- globalWindowManager.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- globalWindowManager.trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
- Trace.endSection()
- }
}
companion object {
private const val LOG_TAG = "ResourceTrimmer"
- private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index 9b07675f..756c6c2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -57,7 +57,7 @@
@Background private val scope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
- private val keyguardInteractor: KeyguardInteractor,
+ keyguardInteractor: KeyguardInteractor,
private val communalInteractor: CommunalInteractor,
powerInteractor: PowerInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -70,6 +70,7 @@
bgDispatcher = bgDispatcher,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ keyguardInteractor = keyguardInteractor,
) {
override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 01109af..2a9ee9f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -48,7 +48,7 @@
@Background private val scope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
- private val keyguardInteractor: KeyguardInteractor,
+ keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
val deviceEntryRepository: DeviceEntryRepository,
@@ -60,6 +60,7 @@
bgDispatcher = bgDispatcher,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ keyguardInteractor = keyguardInteractor,
) {
override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 7d3de30..f5e98f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -47,7 +47,7 @@
@Background private val scope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
- private val keyguardInteractor: KeyguardInteractor,
+ keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -60,6 +60,7 @@
bgDispatcher = bgDispatcher,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ keyguardInteractor = keyguardInteractor,
) {
override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
index 63294f7..47aa02a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingLockscreenHostedTransitionInteractor.kt
@@ -45,7 +45,7 @@
@Background private val scope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
- private val keyguardInteractor: KeyguardInteractor,
+ keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
) :
@@ -56,6 +56,7 @@
bgDispatcher = bgDispatcher,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ keyguardInteractor = keyguardInteractor,
) {
override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 7961b45..25c3b0d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
@@ -51,7 +51,7 @@
@Background private val scope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
- private val keyguardInteractor: KeyguardInteractor,
+ keyguardInteractor: KeyguardInteractor,
private val glanceableHubTransitions: GlanceableHubTransitions,
powerInteractor: PowerInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -63,6 +63,7 @@
bgDispatcher = bgDispatcher,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ keyguardInteractor = keyguardInteractor,
) {
override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index ca6ab3e..e516fa3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -48,7 +48,7 @@
@Main mainDispatcher: CoroutineDispatcher,
@Background bgDispatcher: CoroutineDispatcher,
private val glanceableHubTransitions: GlanceableHubTransitions,
- private val keyguardInteractor: KeyguardInteractor,
+ keyguardInteractor: KeyguardInteractor,
override val transitionRepository: KeyguardTransitionRepository,
transitionInteractor: KeyguardTransitionInteractor,
powerInteractor: PowerInteractor,
@@ -61,6 +61,7 @@
bgDispatcher = bgDispatcher,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ keyguardInteractor = keyguardInteractor,
) {
override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
index 8ca29c8..a540d76 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt
@@ -49,7 +49,7 @@
@Background private val scope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
- private val keyguardInteractor: KeyguardInteractor,
+ keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -64,6 +64,7 @@
bgDispatcher = bgDispatcher,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ keyguardInteractor = keyguardInteractor,
) {
override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
index f1e98f3..8cab3cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt
@@ -58,7 +58,7 @@
@Background private val scope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
- private val keyguardInteractor: KeyguardInteractor,
+ keyguardInteractor: KeyguardInteractor,
private val flags: FeatureFlags,
private val shadeRepository: ShadeRepository,
powerInteractor: PowerInteractor,
@@ -73,6 +73,7 @@
bgDispatcher = bgDispatcher,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ keyguardInteractor = keyguardInteractor,
) {
override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
index 2603aab2..86d4cfb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromOccludedTransitionInteractor.kt
@@ -45,7 +45,7 @@
@Background private val scope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
- private val keyguardInteractor: KeyguardInteractor,
+ keyguardInteractor: KeyguardInteractor,
powerInteractor: PowerInteractor,
private val communalInteractor: CommunalInteractor,
keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -57,6 +57,7 @@
bgDispatcher = bgDispatcher,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ keyguardInteractor = keyguardInteractor,
) {
override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
index 76a8223..19b2b81 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromPrimaryBouncerTransitionInteractor.kt
@@ -52,7 +52,7 @@
@Background private val scope: CoroutineScope,
@Background bgDispatcher: CoroutineDispatcher,
@Main mainDispatcher: CoroutineDispatcher,
- private val keyguardInteractor: KeyguardInteractor,
+ keyguardInteractor: KeyguardInteractor,
private val communalInteractor: CommunalInteractor,
private val flags: FeatureFlags,
private val keyguardSecurityModel: KeyguardSecurityModel,
@@ -67,6 +67,7 @@
bgDispatcher = bgDispatcher,
powerInteractor = powerInteractor,
keyguardOcclusionInteractor = keyguardOcclusionInteractor,
+ keyguardInteractor = keyguardInteractor,
) {
override fun start() {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index c44a40f..b142b44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -37,6 +37,7 @@
import com.android.systemui.keyguard.shared.model.DozeStateModel
import com.android.systemui.keyguard.shared.model.DozeStateModel.Companion.isDozeOff
import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -47,8 +48,10 @@
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.shade.data.repository.ShadeRepository
import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.notification.NotificationUtils.interpolate
import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
+import com.android.systemui.util.kotlin.pairwise
import com.android.systemui.util.kotlin.sample
import javax.inject.Inject
import javax.inject.Provider
@@ -66,7 +69,9 @@
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
@@ -96,17 +101,54 @@
// TODO(b/296118689): move to a repository
private val _notificationPlaceholderBounds = MutableStateFlow(NotificationContainerBounds())
+ // When going to AOD, we interpolate bounds when receiving the new bounds
+ // When going back to LS, we'll apply new bounds directly
+ private val _nonSplitShadeNotifciationPlaceholderBounds =
+ _notificationPlaceholderBounds.pairwise().flatMapLatest { (oldBounds, newBounds) ->
+ val lastChangeStep = keyguardTransitionInteractor.transitionState.first()
+ if (lastChangeStep.to == AOD) {
+ keyguardTransitionInteractor.transitionState.map { step ->
+ val startingProgress = lastChangeStep.value
+ val progress = step.value
+ if (step.to == AOD && progress >= startingProgress) {
+ val adjustedProgress =
+ ((progress - startingProgress) / (1F - startingProgress)).coerceIn(
+ 0F,
+ 1F
+ )
+ val top = interpolate(oldBounds.top, newBounds.top, adjustedProgress)
+ val bottom =
+ interpolate(
+ oldBounds.bottom,
+ newBounds.bottom,
+ adjustedProgress.coerceIn(0F, 1F)
+ )
+ NotificationContainerBounds(top = top, bottom = bottom)
+ } else {
+ newBounds
+ }
+ }
+ } else {
+ flow { emit(newBounds) }
+ }
+ }
+
/** Bounds of the notification container. */
val notificationContainerBounds: StateFlow<NotificationContainerBounds> by lazy {
SceneContainerFlag.assertInLegacyMode()
combine(
_notificationPlaceholderBounds,
+ _nonSplitShadeNotifciationPlaceholderBounds,
sharedNotificationContainerInteractor.get().configurationBasedDimensions,
- ) { bounds, cfg ->
+ ) { bounds, nonSplitShadeBounds: NotificationContainerBounds, cfg ->
// We offset the placeholder bounds by the configured top margin to account for
// legacy placement behavior within notifications for splitshade.
- if (MigrateClocksToBlueprint.isEnabled && cfg.useSplitShade) {
- bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin)
+ if (MigrateClocksToBlueprint.isEnabled) {
+ if (cfg.useSplitShade) {
+ bounds.copy(bottom = bounds.bottom - cfg.keyguardSplitShadeTopMargin)
+ } else {
+ nonSplitShadeBounds
+ }
} else bounds
}
.stateIn(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index ccce3bf..8ffa4bb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -105,33 +105,35 @@
}
return combine(
- quickAffordanceAlwaysVisible(position),
- keyguardInteractor.isDozing,
- if (SceneContainerFlag.isEnabled) {
- sceneInteractor
- .get()
- .transitionState
- .map {
- when (it) {
- is ObservableTransitionState.Idle ->
- it.currentScene == Scenes.Lockscreen
- is ObservableTransitionState.Transition ->
- it.fromScene == Scenes.Lockscreen || it.toScene == Scenes.Lockscreen
+ quickAffordanceAlwaysVisible(position),
+ keyguardInteractor.isDozing,
+ if (SceneContainerFlag.isEnabled) {
+ sceneInteractor
+ .get()
+ .transitionState
+ .map {
+ when (it) {
+ is ObservableTransitionState.Idle ->
+ it.currentScene == Scenes.Lockscreen
+ is ObservableTransitionState.Transition ->
+ it.fromScene == Scenes.Lockscreen ||
+ it.toScene == Scenes.Lockscreen
+ }
}
- }
- .distinctUntilChanged()
- } else {
- keyguardInteractor.isKeyguardShowing
- },
- shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(),
- biometricSettingsRepository.isCurrentUserInLockdown,
- ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
- if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) {
- affordance
- } else {
- KeyguardQuickAffordanceModel.Hidden
+ .distinctUntilChanged()
+ } else {
+ keyguardInteractor.isKeyguardShowing
+ },
+ shadeInteractor.anyExpansion.map { it < 1.0f }.distinctUntilChanged(),
+ biometricSettingsRepository.isCurrentUserInLockdown,
+ ) { affordance, isDozing, isKeyguardShowing, isQuickSettingsVisible, isUserInLockdown ->
+ if (!isDozing && isKeyguardShowing && isQuickSettingsVisible && !isUserInLockdown) {
+ affordance
+ } else {
+ KeyguardQuickAffordanceModel.Hidden
+ }
}
- }
+ .distinctUntilChanged()
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
index 323ceef..e148207 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/TransitionInteractor.kt
@@ -53,6 +53,7 @@
val bgDispatcher: CoroutineDispatcher,
val powerInteractor: PowerInteractor,
val keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
+ val keyguardInteractor: KeyguardInteractor,
) {
val name = this::class.simpleName ?: "UnknownTransitionInteractor"
abstract val transitionRepository: KeyguardTransitionRepository
@@ -164,14 +165,10 @@
@Deprecated("Will be merged into maybeStartTransitionToOccludedOrInsecureCamera")
suspend fun maybeHandleInsecurePowerGesture(): Boolean {
if (keyguardOcclusionInteractor.shouldTransitionFromPowerButtonGesture()) {
- if (transitionInteractor.getCurrentState() == KeyguardState.GONE) {
- // If the current state is GONE when the launch gesture is triggered, it means we
- // were in transition from GONE -> DOZING/AOD due to the first power button tap. The
- // second tap indicates that the user's intent was actually to launch the unlocked
- // (insecure) camera, so we should transition back to GONE.
+ if (keyguardInteractor.isKeyguardDismissible.value) {
startTransitionTo(
KeyguardState.GONE,
- ownerReason = "Power button gesture while GONE"
+ ownerReason = "Power button gesture while keyguard is dismissible"
)
return true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
index 6d579f3..f5b82cf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardBlueprint.kt
@@ -40,6 +40,7 @@
rebuildSections: List<KeyguardSection> = listOf(),
bindData: Boolean = true
) {
+ rebuildSections.forEach { it.onRebuildBegin() }
val prevSections = previousBlueprint?.sections ?: listOf()
val skipSections = sections.intersect(prevSections).subtract(rebuildSections)
prevSections.subtract(skipSections).forEach { it.removeViews(constraintLayout) }
@@ -49,6 +50,7 @@
it.bindData(constraintLayout)
}
}
+ rebuildSections.forEach { it.onRebuildEnd() }
}
/** Rebuilds views for the target sections, or all of them if unspecified. */
@@ -61,6 +63,7 @@
return
}
+ rebuildSections.forEach { it.onRebuildBegin() }
rebuildSections.forEach { it.removeViews(constraintLayout) }
rebuildSections.forEach {
it.addViews(constraintLayout)
@@ -68,6 +71,7 @@
it.bindData(constraintLayout)
}
}
+ rebuildSections.forEach { it.onRebuildEnd() }
}
fun applyConstraints(constraintSet: ConstraintSet) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
index 48a2146..473b08f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSection.kt
@@ -33,6 +33,12 @@
/** Removes views and does any data binding destruction. */
abstract fun removeViews(constraintLayout: ConstraintLayout)
+ /* Notifies the section is being rebuilt */
+ open fun onRebuildBegin() {}
+
+ /* Notifies the secion that the rebuild is complete */
+ open fun onRebuildEnd() {}
+
/**
* Defines equality as same class.
*
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 23c2491..807c322 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -65,7 +65,7 @@
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
val disposableHandle =
view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
launch("$TAG#viewModel.alpha") {
// Do not independently apply alpha, as [KeyguardRootViewModel] should work
// for this and all its children
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index b9a79dc..1cf009d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -30,6 +30,7 @@
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.repeatOnLifecycle
+import com.android.app.tracing.coroutines.launch
import com.android.settingslib.Utils
import com.android.systemui.animation.Expandable
import com.android.systemui.animation.view.LaunchableImageView
@@ -80,8 +81,8 @@
val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
val disposableHandle =
view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- launch {
+ repeatOnLifecycle(Lifecycle.State.CREATED) {
+ launch("$TAG#viewModel.collect") {
viewModel.collect { buttonModel ->
updateButton(
view = button,
@@ -93,7 +94,7 @@
}
}
- launch {
+ launch("$TAG#updateButtonAlpha") {
updateButtonAlpha(
view = button,
viewModel = viewModel,
@@ -101,7 +102,7 @@
)
}
- launch {
+ launch("$TAG#configurationBasedDimensions") {
configurationBasedDimensions.collect { dimensions ->
button.updateLayoutParams<ViewGroup.LayoutParams> {
width = dimensions.buttonSizePx.width
@@ -323,4 +324,6 @@
private data class ConfigurationBasedDimensions(
val buttonSizePx: Size,
)
+
+ private const val TAG = "KeyguardQuickAffordanceViewBinder"
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
index c5fab8f..e01f0a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt
@@ -147,8 +147,9 @@
deviceEntryIconViewModel.get().udfpsLocation.value?.let { udfpsLocation ->
Log.d(
"DeviceEntrySection",
- "udfpsLocation=$udfpsLocation" +
- " unusedAuthController=${authController.udfpsLocation}"
+ "udfpsLocation=$udfpsLocation, " +
+ "scaledLocation=(${udfpsLocation.centerX},${udfpsLocation.centerY}), " +
+ "unusedAuthController=${authController.udfpsLocation}"
)
centerIcon(
Point(udfpsLocation.centerX.toInt(), udfpsLocation.centerY.toInt()),
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 2d6690f..0435531 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -56,6 +56,14 @@
private var smartspaceVisibilityListener: OnGlobalLayoutListener? = null
private var pastVisibility: Int = -1
+ override fun onRebuildBegin() {
+ smartspaceController.suppressDisconnects = true
+ }
+
+ override fun onRebuildEnd() {
+ smartspaceController.suppressDisconnects = false
+ }
+
override fun addViews(constraintLayout: ConstraintLayout) {
if (!MigrateClocksToBlueprint.isEnabled) return
if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) return
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 198e9f2..940f423 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
@@ -65,7 +65,7 @@
}
.stateIn(
scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
+ started = SharingStarted.Eagerly,
initialValue = ClockSize.LARGE,
)
@@ -74,7 +74,7 @@
.map { it == ClockSize.LARGE }
.stateIn(
scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
+ started = SharingStarted.Eagerly,
initialValue = true,
)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
index 8409f15..448a71c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModel.kt
@@ -23,9 +23,12 @@
import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.res.R
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -42,6 +45,7 @@
private val burnInInteractor: BurnInInteractor,
private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel,
configurationInteractor: ConfigurationInteractor,
+ keyguardTransitionInteractor: KeyguardTransitionInteractor,
) {
/** Notifies when a new configuration is set */
@@ -69,12 +73,22 @@
.distinctUntilChanged()
}
+ @OptIn(ExperimentalCoroutinesApi::class)
private val burnIn: Flow<BurnInModel> =
- burnInInteractor
- .burnIn(
- xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
- yDimenResourceId = R.dimen.default_burn_in_prevention_offset,
- )
+ combine(
+ burnInInteractor.burnIn(
+ xDimenResourceId = R.dimen.burn_in_prevention_offset_x,
+ yDimenResourceId = R.dimen.default_burn_in_prevention_offset,
+ ),
+ keyguardTransitionInteractor.transitionValue(KeyguardState.AOD),
+ ) { burnIn, aodTransitionValue ->
+ BurnInModel(
+ (burnIn.translationX * aodTransitionValue).toInt(),
+ (burnIn.translationY * aodTransitionValue).toInt(),
+ burnIn.scale,
+ burnIn.scaleClockOnly,
+ )
+ }
.distinctUntilChanged()
/** An observable for the x-offset by which the indication area should be translated. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index c4383fc..244d842 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import androidx.annotation.VisibleForTesting
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -28,19 +29,23 @@
import com.android.systemui.shade.domain.interactor.ShadeInteractor
import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
@OptIn(ExperimentalCoroutinesApi::class)
class KeyguardQuickAffordancesCombinedViewModel
@Inject
constructor(
+ @Application applicationScope: CoroutineScope,
private val quickAffordanceInteractor: KeyguardQuickAffordanceInteractor,
private val keyguardInteractor: KeyguardInteractor,
shadeInteractor: ShadeInteractor,
@@ -84,15 +89,20 @@
/** The only time the expansion is important is while lockscreen is actively displayed */
private val shadeExpansionAlpha =
combine(
- showingLockscreen,
- shadeInteractor.anyExpansion,
- ) { showingLockscreen, expansion ->
- if (showingLockscreen) {
- 1 - expansion
- } else {
- 0f
+ showingLockscreen,
+ shadeInteractor.anyExpansion,
+ ) { showingLockscreen, expansion ->
+ if (showingLockscreen) {
+ 1 - expansion
+ } else {
+ 0f
+ }
}
- }
+ .stateIn(
+ scope = applicationScope,
+ started = SharingStarted.Lazily,
+ initialValue = 0f,
+ )
/**
* ID of the slot that's currently selected in the preview that renders exclusively in the
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
index e0c5419..9c29bab 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt
@@ -43,6 +43,7 @@
@IntoMap
@ClassKey(MediaDataProcessor::class)
fun bindMediaDataProcessor(interactor: MediaDataProcessor): CoreStartable
+
companion object {
@Provides
@@ -52,7 +53,7 @@
newProvider: Provider<MediaCarouselInteractor>,
mediaFlags: MediaFlags,
): MediaDataManager {
- return if (mediaFlags.isMediaControlsRefactorEnabled()) {
+ return if (mediaFlags.isSceneContainerEnabled()) {
newProvider.get()
} else {
legacyProvider.get()
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
index eed7752..8e985e1 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessor.kt
@@ -269,7 +269,7 @@
}
override fun start() {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (!mediaFlags.isSceneContainerEnabled()) {
return
}
@@ -746,8 +746,7 @@
notif.extras.getParcelable(
Notification.EXTRA_BUILDER_APPLICATION_INFO,
ApplicationInfo::class.java
- )
- ?: getAppInfoFromPackage(sbn.packageName)
+ ) ?: getAppInfoFromPackage(sbn.packageName)
// App name
val appName = getAppName(sbn, appInfo)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
index 9e62300..b4bd4fd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/domain/pipeline/interactor/MediaCarouselInteractor.kt
@@ -36,8 +36,8 @@
import com.android.systemui.media.controls.domain.pipeline.MediaTimeoutListener
import com.android.systemui.media.controls.domain.resume.MediaResumeListener
import com.android.systemui.media.controls.shared.model.MediaCommonModel
-import com.android.systemui.media.controls.util.MediaControlsRefactorFlag
import com.android.systemui.media.controls.util.MediaFlags
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
import java.io.PrintWriter
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
@@ -127,7 +127,7 @@
val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia
override fun start() {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (!mediaFlags.isSceneContainerEnabled()) {
return
}
@@ -256,8 +256,6 @@
companion object {
val unsupported: Nothing
get() =
- error(
- "Code path not supported when ${MediaControlsRefactorFlag.FLAG_NAME} is enabled"
- )
+ error("Code path not supported when ${SceneContainerFlag.DESCRIPTION} is enabled")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 19e3e07..7bacdeb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -42,6 +42,7 @@
import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.Dumpable
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.dump.DumpManager
@@ -123,6 +124,7 @@
class MediaCarouselController
@Inject
constructor(
+ @Application applicationScope: CoroutineScope,
private val context: Context,
private val mediaControlPanelFactory: Provider<MediaControlPanel>,
private val visualStabilityProvider: VisualStabilityProvider,
@@ -217,7 +219,7 @@
private val animationScaleObserver: ContentObserver =
object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (!mediaFlags.isSceneContainerEnabled()) {
MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() }
} else {
controllerByViewModel.values.forEach { it.updateAnimatorDurationScale() }
@@ -347,7 +349,7 @@
inflateSettingsButton()
mediaContent = mediaCarousel.requireViewById(R.id.media_carousel)
configurationController.addCallback(configListener)
- if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (!mediaFlags.isSceneContainerEnabled()) {
setUpListeners()
} else {
val visualStabilityCallback = OnReorderingAllowedListener {
@@ -387,12 +389,12 @@
repeatOnLifecycle(Lifecycle.State.STARTED) {
listenForAnyStateToGoneKeyguardTransition(this)
listenForAnyStateToLockscreenTransition(this)
- listenForLockscreenSettingChanges(this)
- if (!mediaFlags.isMediaControlsRefactorEnabled()) return@repeatOnLifecycle
+ if (!mediaFlags.isSceneContainerEnabled()) return@repeatOnLifecycle
listenForMediaItemsChanges(this)
}
}
+ listenForLockscreenSettingChanges(applicationScope)
// Notifies all active players about animation scale changes.
globalSettings.registerContentObserver(
@@ -882,8 +884,7 @@
val previousVisibleIndex =
MediaPlayerData.playerKeys().indexOfFirst { key -> it == key }
mediaCarouselScrollHandler.scrollToPlayer(previousVisibleIndex, mediaIndex)
- }
- ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
+ } ?: mediaCarouselScrollHandler.scrollToPlayer(destIndex = mediaIndex)
}
} else if (isRtl && mediaContent.childCount > 0) {
// In RTL, Scroll to the first player as it is the rightmost player in media carousel.
@@ -1092,7 +1093,7 @@
}
private fun updatePlayers(recreateMedia: Boolean) {
- if (mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (mediaFlags.isSceneContainerEnabled()) {
updateMediaPlayers(recreateMedia)
return
}
@@ -1192,7 +1193,7 @@
currentStartLocation = startLocation
currentEndLocation = endLocation
currentTransitionProgress = progress
- if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (!mediaFlags.isSceneContainerEnabled()) {
for (mediaPlayer in MediaPlayerData.players()) {
updateViewControllerToState(mediaPlayer.mediaViewController, immediately)
}
@@ -1254,7 +1255,7 @@
/** Update listening to seekbar. */
private fun updateSeekbarListening(visibleToUser: Boolean) {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (!mediaFlags.isSceneContainerEnabled()) {
for (player in MediaPlayerData.players()) {
player.setListening(visibleToUser && currentlyExpanded)
}
@@ -1269,7 +1270,7 @@
private fun updateCarouselDimensions() {
var width = 0
var height = 0
- if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (!mediaFlags.isSceneContainerEnabled()) {
for (mediaPlayer in MediaPlayerData.players()) {
val controller = mediaPlayer.mediaViewController
// When transitioning the view to gone, the view gets smaller, but the translation
@@ -1361,7 +1362,7 @@
!mediaManager.hasActiveMediaOrRecommendation() &&
desiredHostState.showsOnlyActiveMedia
- if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (!mediaFlags.isSceneContainerEnabled()) {
for (mediaPlayer in MediaPlayerData.players()) {
if (animate) {
mediaPlayer.mediaViewController.animatePendingStateChange(
@@ -1401,7 +1402,7 @@
}
fun closeGuts(immediate: Boolean = true) {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (!mediaFlags.isSceneContainerEnabled()) {
MediaPlayerData.players().forEach { it.closeGuts(immediate) }
} else {
controllerByViewModel.values.forEach { it.closeGuts(immediate) }
@@ -1544,7 +1545,7 @@
@VisibleForTesting
fun onSwipeToDismiss() {
- if (mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (mediaFlags.isSceneContainerEnabled()) {
mediaCarouselViewModel.onSwipeToDismiss()
return
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
index a4f3e21..6589038 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManager.kt
@@ -42,6 +42,7 @@
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
import com.android.systemui.media.controls.ui.view.MediaHost
import com.android.systemui.media.controls.util.MediaFlags
@@ -61,6 +62,11 @@
import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
private val TAG: String = MediaHierarchyManager::class.java.simpleName
@@ -89,6 +95,7 @@
* This manager is responsible for placement of the unique media view between the different hosts
* and animate the positions of the views to achieve seamless transitions.
*/
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class MediaHierarchyManager
@Inject
@@ -101,6 +108,7 @@
private val mediaManager: MediaDataManager,
private val keyguardViewController: KeyguardViewController,
private val dreamOverlayStateController: DreamOverlayStateController,
+ private val keyguardInteractor: KeyguardInteractor,
communalTransitionViewModel: CommunalTransitionViewModel,
configurationController: ConfigurationController,
wakefulnessLifecycle: WakefulnessLifecycle,
@@ -236,6 +244,15 @@
private var inSplitShade = false
+ /**
+ * Whether we are transitioning to the hub or from the hub to the shade. If so, use fade as the
+ * transformation type and skip calculating state with the bounds and the transition progress.
+ */
+ private val isHubTransition
+ get() =
+ desiredLocation == LOCATION_COMMUNAL_HUB ||
+ (previousLocation == LOCATION_COMMUNAL_HUB && desiredLocation == LOCATION_QS)
+
/** Is there any active media or recommendation in the carousel? */
private var hasActiveMediaOrRecommendation: Boolean = false
get() = mediaManager.hasActiveMediaOrRecommendation()
@@ -413,6 +430,12 @@
/** Is the communal UI showing */
private var isCommunalShowing: Boolean = false
+ /** Is the communal UI showing and not dreaming */
+ private var onCommunalNotDreaming: Boolean = false
+
+ /** Is the communal UI showing, dreaming and shade expanding */
+ private var onCommunalDreamingAndShadeExpanding: Boolean = false
+
/**
* The current cross fade progress. 0.5f means it's just switching between the start and the end
* location and the content is fully faded, while 0.75f means that we're halfway faded in again
@@ -585,11 +608,26 @@
// Listen to the communal UI state. Make sure that communal UI is showing and hub itself is
// available, ie. not disabled and able to be shown.
+ // When dreaming, qs expansion is immediately set to 1f, so we listen to shade expansion to
+ // calculate the new location.
coroutineScope.launch {
- communalTransitionViewModel.isUmoOnCommunal.collect { value ->
- isCommunalShowing = value
- updateDesiredLocation(forceNoAnimation = true)
- }
+ combine(
+ communalTransitionViewModel.isUmoOnCommunal,
+ keyguardInteractor.isDreaming,
+ // keep on communal before the shade is expanded enough to show the elements in
+ // QS
+ shadeInteractor.shadeExpansion
+ .mapLatest { it < EXPANSION_THRESHOLD }
+ .distinctUntilChanged(),
+ ::Triple
+ )
+ .collectLatest { (communalShowing, isDreaming, isShadeExpanding) ->
+ isCommunalShowing = communalShowing
+ onCommunalDreamingAndShadeExpanding =
+ communalShowing && isDreaming && isShadeExpanding
+ onCommunalNotDreaming = communalShowing && !isDreaming
+ updateDesiredLocation(forceNoAnimation = true)
+ }
}
}
@@ -805,6 +843,9 @@
if (skipQqsOnExpansion) {
return false
}
+ if (isHubTransition) {
+ return false
+ }
// This is an invalid transition, and can happen when using the camera gesture from the
// lock screen. Disallow.
if (
@@ -947,6 +988,9 @@
@VisibleForTesting
@TransformationType
fun calculateTransformationType(): Int {
+ if (isHubTransition) {
+ return TRANSFORMATION_TYPE_FADE
+ }
if (isTransitioningToFullShade) {
if (inSplitShade && areGuidedTransitionHostsVisible()) {
return TRANSFORMATION_TYPE_TRANSITION
@@ -977,7 +1021,7 @@
* otherwise
*/
private fun getTransformationProgress(): Float {
- if (skipQqsOnExpansion) {
+ if (skipQqsOnExpansion || isHubTransition) {
return -1.0f
}
val progress = getQSTransformationProgress()
@@ -1147,15 +1191,18 @@
}
val onLockscreen =
(!bypassController.bypassEnabled && (statusbarState == StatusBarState.KEYGUARD))
+
+ // UMO should show on hub unless the qs is expanding when not dreaming, or shade is
+ // expanding when dreaming
+ val onCommunal =
+ (onCommunalNotDreaming && qsExpansion == 0.0f) || onCommunalDreamingAndShadeExpanding
val location =
when {
mediaFlags.isSceneContainerEnabled() -> desiredLocation
dreamOverlayActive && dreamMediaComplicationActive -> LOCATION_DREAM_OVERLAY
-
- // UMO should show in communal unless the shade is expanding or visible.
- isCommunalShowing && qsExpansion == 0.0f -> LOCATION_COMMUNAL_HUB
+ onCommunal -> LOCATION_COMMUNAL_HUB
(qsExpansion > 0.0f || inSplitShade) && !onLockscreen -> LOCATION_QS
- qsExpansion > 0.4f && onLockscreen -> LOCATION_QS
+ qsExpansion > EXPANSION_THRESHOLD && onLockscreen -> LOCATION_QS
onLockscreen && isSplitShadeExpanding() -> LOCATION_QS
onLockscreen && isTransformingToFullShadeAndInQQS() -> LOCATION_QQS
@@ -1190,6 +1237,9 @@
// reattach it without an animation
return LOCATION_LOCKSCREEN
}
+ // When communal showing while dreaming, skipQqsOnExpansion is also true but we want to
+ // return the calculated location, so it won't disappear as soon as shade is pulled down.
+ if (isCommunalShowing) return location
if (skipQqsOnExpansion) {
// When doing an immediate expand or collapse, we want to keep it in QS.
return LOCATION_QS
@@ -1288,6 +1338,9 @@
* transitioning
*/
const val TRANSFORMATION_TYPE_FADE = 1
+
+ /** Expansion amount value at which elements start to become visible in the QS panel. */
+ const val EXPANSION_THRESHOLD = 0.4f
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 3837708..9d07232 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -203,7 +203,7 @@
private val scrubbingChangeListener =
object : SeekBarViewModel.ScrubbingChangeListener {
override fun onScrubbingChanged(scrubbing: Boolean) {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (!mediaFlags.isSceneContainerEnabled()) return
if (isScrubbing == scrubbing) return
isScrubbing = scrubbing
updateDisplayForScrubbingChange()
@@ -213,7 +213,7 @@
private val enabledChangeListener =
object : SeekBarViewModel.EnabledChangeListener {
override fun onEnabledChanged(enabled: Boolean) {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (!mediaFlags.isSceneContainerEnabled()) return
if (isSeekBarEnabled == enabled) return
isSeekBarEnabled = enabled
MediaControlViewBinder.updateSeekBarVisibility(expandedLayout, isSeekBarEnabled)
@@ -229,7 +229,7 @@
* @param listening True when player should be active. Otherwise, false.
*/
fun setListening(listening: Boolean) {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (!mediaFlags.isSceneContainerEnabled()) return
seekBarViewModel.listening = listening
}
@@ -263,7 +263,7 @@
)
)
}
- if (mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (mediaFlags.isSceneContainerEnabled()) {
if (
this@MediaViewController::recsConfigurationChangeListener.isInitialized
) {
@@ -305,6 +305,7 @@
*/
var collapsedLayout = ConstraintSet()
@VisibleForTesting set
+
/**
* The expanded constraint set used to render a collapsed player. If it is modified, make sure
* to call [refreshState]
@@ -334,7 +335,7 @@
* Notify this controller that the view has been removed and all listeners should be destroyed
*/
fun onDestroy() {
- if (mediaFlags.isMediaControlsRefactorEnabled()) {
+ if (mediaFlags.isSceneContainerEnabled()) {
if (this::seekBarObserver.isInitialized) {
seekBarViewModel.progress.removeObserver(seekBarObserver)
}
@@ -657,7 +658,7 @@
}
fun attachPlayer(mediaViewHolder: MediaViewHolder) {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (!mediaFlags.isSceneContainerEnabled()) return
this.mediaViewHolder = mediaViewHolder
// Setting up seek bar.
@@ -731,7 +732,7 @@
}
fun updateAnimatorDurationScale() {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (!mediaFlags.isSceneContainerEnabled()) return
if (this::seekBarObserver.isInitialized) {
seekBarObserver.animationEnabled =
globalSettings.getFloat(Settings.Global.ANIMATOR_DURATION_SCALE, 1f) > 0f
@@ -787,7 +788,7 @@
}
fun attachRecommendations(recommendationViewHolder: RecommendationViewHolder) {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (!mediaFlags.isSceneContainerEnabled()) return
this.recommendationViewHolder = recommendationViewHolder
attach(recommendationViewHolder.recommendations, TYPE.RECOMMENDATION)
@@ -796,13 +797,13 @@
}
fun bindSeekBar(onSeek: () -> Unit, onBindSeekBar: (SeekBarViewModel) -> Unit) {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (!mediaFlags.isSceneContainerEnabled()) return
seekBarViewModel.logSeek = onSeek
onBindSeekBar.invoke(seekBarViewModel)
}
fun setUpTurbulenceNoise() {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (!mediaFlags.isSceneContainerEnabled()) return
if (!this::turbulenceNoiseAnimationConfig.isInitialized) {
turbulenceNoiseAnimationConfig =
createTurbulenceNoiseConfig(
@@ -1153,13 +1154,13 @@
}
fun setUpPrevButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (!mediaFlags.isSceneContainerEnabled()) return
isPrevButtonAvailable = isAvailable
prevNotVisibleValue = notVisibleValue
}
fun setUpNextButtonInfo(isAvailable: Boolean, notVisibleValue: Int = ConstraintSet.GONE) {
- if (!mediaFlags.isMediaControlsRefactorEnabled()) return
+ if (!mediaFlags.isSceneContainerEnabled()) return
isNextButtonAvailable = isAvailable
nextNotVisibleValue = notVisibleValue
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt
deleted file mode 100644
index 2850b4b..0000000
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.controls.util
-
-import com.android.systemui.Flags
-import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
-
-/** Helper for reading or using the media_controls_refactor flag state. */
-@Suppress("NOTHING_TO_INLINE")
-object MediaControlsRefactorFlag {
- /** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_MEDIA_CONTROLS_REFACTOR
-
- /** A token used for dependency declaration */
- val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
-
- /** Is the flag enabled? */
- @JvmStatic
- inline val isEnabled
- get() = Flags.mediaControlsRefactor()
-
- /**
- * Called to ensure code is only run when the flag is enabled. This protects users from the
- * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
- * build to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
-
- /**
- * Called to ensure code is only run when the flag is disabled. This will throw an exception if
- * the flag is enabled to ensure that the refactor author catches issues in testing.
- */
- @JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
index 1e7bc0c..21c3111 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt
@@ -52,8 +52,4 @@
/** Check whether to use scene framework */
fun isSceneContainerEnabled() = SceneContainerFlag.isEnabled
-
- /** Check whether to use media refactor code */
- fun isMediaControlsRefactorEnabled() =
- MediaControlsRefactorFlag.isEnabled && SceneContainerFlag.isEnabled
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
index a256b59..e931f8f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -18,10 +18,7 @@
private const val TAG = "BackPanel"
private const val DEBUG = false
-class BackPanel(
- context: Context,
- private val latencyTracker: LatencyTracker
-) : View(context) {
+class BackPanel(context: Context, private val latencyTracker: LatencyTracker) : View(context) {
var arrowsPointLeft = false
set(value) {
@@ -42,39 +39,39 @@
// True if the panel is currently on the left of the screen
var isLeftPanel = false
- /**
- * Used to track back arrow latency from [android.view.MotionEvent.ACTION_DOWN] to [onDraw]
- */
+ /** Used to track back arrow latency from [android.view.MotionEvent.ACTION_DOWN] to [onDraw] */
private var trackingBackArrowLatency = false
- /**
- * The length of the arrow measured horizontally. Used for animating [arrowPath]
- */
- private var arrowLength = AnimatedFloat(
+ /** The length of the arrow measured horizontally. Used for animating [arrowPath] */
+ private var arrowLength =
+ AnimatedFloat(
name = "arrowLength",
minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS
- )
+ )
/**
* The height of the arrow measured vertically from its center to its top (i.e. half the total
* height). Used for animating [arrowPath]
*/
- var arrowHeight = AnimatedFloat(
+ var arrowHeight =
+ AnimatedFloat(
name = "arrowHeight",
minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ROTATION_DEGREES
- )
+ )
- val backgroundWidth = AnimatedFloat(
+ val backgroundWidth =
+ AnimatedFloat(
name = "backgroundWidth",
minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
minimumValue = 0f,
- )
+ )
- val backgroundHeight = AnimatedFloat(
+ val backgroundHeight =
+ AnimatedFloat(
name = "backgroundHeight",
minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
minimumValue = 0f,
- )
+ )
/**
* Corners of the background closer to the edge of the screen (where the arrow appeared from).
@@ -88,17 +85,19 @@
*/
val backgroundFarCornerRadius = AnimatedFloat("backgroundFarCornerRadius")
- var scale = AnimatedFloat(
+ var scale =
+ AnimatedFloat(
name = "scale",
minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_SCALE,
minimumValue = 0f
- )
+ )
- val scalePivotX = AnimatedFloat(
+ val scalePivotX =
+ AnimatedFloat(
name = "scalePivotX",
minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_PIXELS,
minimumValue = backgroundWidth.pos / 2,
- )
+ )
/**
* Left/right position of the background relative to the canvas. Also corresponds with the
@@ -107,21 +106,24 @@
*/
var horizontalTranslation = AnimatedFloat(name = "horizontalTranslation")
- var arrowAlpha = AnimatedFloat(
+ var arrowAlpha =
+ AnimatedFloat(
name = "arrowAlpha",
minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ALPHA,
minimumValue = 0f,
maximumValue = 1f
- )
+ )
- val backgroundAlpha = AnimatedFloat(
+ val backgroundAlpha =
+ AnimatedFloat(
name = "backgroundAlpha",
minimumVisibleChange = SpringAnimation.MIN_VISIBLE_CHANGE_ALPHA,
minimumValue = 0f,
maximumValue = 1f
- )
+ )
- private val allAnimatedFloat = setOf(
+ private val allAnimatedFloat =
+ setOf(
arrowLength,
arrowHeight,
backgroundWidth,
@@ -132,7 +134,7 @@
horizontalTranslation,
arrowAlpha,
backgroundAlpha
- )
+ )
/**
* Canvas vertical translation. How far up/down the arrow and background appear relative to the
@@ -140,43 +142,45 @@
*/
var verticalTranslation = AnimatedFloat("verticalTranslation")
- /**
- * Use for drawing debug info. Can only be set if [DEBUG]=true
- */
+ /** Use for drawing debug info. Can only be set if [DEBUG]=true */
var drawDebugInfo: ((canvas: Canvas) -> Unit)? = null
set(value) {
if (DEBUG) field = value
}
internal fun updateArrowPaint(arrowThickness: Float) {
-
arrowPaint.strokeWidth = arrowThickness
- val isDeviceInNightTheme = resources.configuration.uiMode and
- Configuration.UI_MODE_NIGHT_MASK == Configuration.UI_MODE_NIGHT_YES
+ val isDeviceInNightTheme =
+ resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
+ Configuration.UI_MODE_NIGHT_YES
- arrowPaint.color = Utils.getColorAttrDefaultColor(context,
+ arrowPaint.color =
+ Utils.getColorAttrDefaultColor(
+ context,
if (isDeviceInNightTheme) {
com.android.internal.R.attr.materialColorOnSecondaryContainer
} else {
com.android.internal.R.attr.materialColorOnSecondaryFixed
}
- )
+ )
- arrowBackgroundPaint.color = Utils.getColorAttrDefaultColor(context,
+ arrowBackgroundPaint.color =
+ Utils.getColorAttrDefaultColor(
+ context,
if (isDeviceInNightTheme) {
com.android.internal.R.attr.materialColorSecondaryContainer
} else {
com.android.internal.R.attr.materialColorSecondaryFixedDim
}
- )
+ )
}
inner class AnimatedFloat(
- name: String,
- private val minimumVisibleChange: Float? = null,
- private val minimumValue: Float? = null,
- private val maximumValue: Float? = null,
+ name: String,
+ private val minimumVisibleChange: Float? = null,
+ private val minimumValue: Float? = null,
+ private val maximumValue: Float? = null,
) {
// The resting position when not stretched by a touch drag
@@ -207,19 +211,21 @@
}
init {
- val floatProp = object : FloatPropertyCompat<AnimatedFloat>(name) {
- override fun setValue(animatedFloat: AnimatedFloat, value: Float) {
- animatedFloat.pos = value
- }
+ val floatProp =
+ object : FloatPropertyCompat<AnimatedFloat>(name) {
+ override fun setValue(animatedFloat: AnimatedFloat, value: Float) {
+ animatedFloat.pos = value
+ }
- override fun getValue(animatedFloat: AnimatedFloat): Float = animatedFloat.pos
- }
- animation = SpringAnimation(this, floatProp).apply {
- spring = SpringForce()
- this@AnimatedFloat.minimumValue?.let { setMinValue(it) }
- this@AnimatedFloat.maximumValue?.let { setMaxValue(it) }
- this@AnimatedFloat.minimumVisibleChange?.let { minimumVisibleChange = it }
- }
+ override fun getValue(animatedFloat: AnimatedFloat): Float = animatedFloat.pos
+ }
+ animation =
+ SpringAnimation(this, floatProp).apply {
+ spring = SpringForce()
+ this@AnimatedFloat.minimumValue?.let { setMinValue(it) }
+ this@AnimatedFloat.maximumValue?.let { setMaxValue(it) }
+ this@AnimatedFloat.minimumVisibleChange?.let { minimumVisibleChange = it }
+ }
}
fun snapTo(newPosition: Float) {
@@ -233,11 +239,10 @@
snapTo(restingPosition)
}
-
fun stretchTo(
- stretchAmount: Float,
- startingVelocity: Float? = null,
- springForce: SpringForce? = null
+ stretchAmount: Float,
+ startingVelocity: Float? = null,
+ springForce: SpringForce? = null
) {
animation.apply {
startingVelocity?.let {
@@ -297,8 +302,8 @@
}
fun addAnimationEndListener(
- animatedFloat: AnimatedFloat,
- endListener: DelayedOnAnimationEndListener
+ animatedFloat: AnimatedFloat,
+ endListener: DelayedOnAnimationEndListener
): Boolean {
return if (animatedFloat.isRunning) {
animatedFloat.addEndListener(endListener)
@@ -314,51 +319,51 @@
}
fun setStretch(
- horizontalTranslationStretchAmount: Float,
- arrowStretchAmount: Float,
- arrowAlphaStretchAmount: Float,
- backgroundAlphaStretchAmount: Float,
- backgroundWidthStretchAmount: Float,
- backgroundHeightStretchAmount: Float,
- edgeCornerStretchAmount: Float,
- farCornerStretchAmount: Float,
- fullyStretchedDimens: EdgePanelParams.BackIndicatorDimens
+ horizontalTranslationStretchAmount: Float,
+ arrowStretchAmount: Float,
+ arrowAlphaStretchAmount: Float,
+ backgroundAlphaStretchAmount: Float,
+ backgroundWidthStretchAmount: Float,
+ backgroundHeightStretchAmount: Float,
+ edgeCornerStretchAmount: Float,
+ farCornerStretchAmount: Float,
+ fullyStretchedDimens: EdgePanelParams.BackIndicatorDimens
) {
horizontalTranslation.stretchBy(
- finalPosition = fullyStretchedDimens.horizontalTranslation,
- amount = horizontalTranslationStretchAmount
+ finalPosition = fullyStretchedDimens.horizontalTranslation,
+ amount = horizontalTranslationStretchAmount
)
arrowLength.stretchBy(
- finalPosition = fullyStretchedDimens.arrowDimens.length,
- amount = arrowStretchAmount
+ finalPosition = fullyStretchedDimens.arrowDimens.length,
+ amount = arrowStretchAmount
)
arrowHeight.stretchBy(
- finalPosition = fullyStretchedDimens.arrowDimens.height,
- amount = arrowStretchAmount
+ finalPosition = fullyStretchedDimens.arrowDimens.height,
+ amount = arrowStretchAmount
)
arrowAlpha.stretchBy(
- finalPosition = fullyStretchedDimens.arrowDimens.alpha,
- amount = arrowAlphaStretchAmount
+ finalPosition = fullyStretchedDimens.arrowDimens.alpha,
+ amount = arrowAlphaStretchAmount
)
backgroundAlpha.stretchBy(
- finalPosition = fullyStretchedDimens.backgroundDimens.alpha,
- amount = backgroundAlphaStretchAmount
+ finalPosition = fullyStretchedDimens.backgroundDimens.alpha,
+ amount = backgroundAlphaStretchAmount
)
backgroundWidth.stretchBy(
- finalPosition = fullyStretchedDimens.backgroundDimens.width,
- amount = backgroundWidthStretchAmount
+ finalPosition = fullyStretchedDimens.backgroundDimens.width,
+ amount = backgroundWidthStretchAmount
)
backgroundHeight.stretchBy(
- finalPosition = fullyStretchedDimens.backgroundDimens.height,
- amount = backgroundHeightStretchAmount
+ finalPosition = fullyStretchedDimens.backgroundDimens.height,
+ amount = backgroundHeightStretchAmount
)
backgroundEdgeCornerRadius.stretchBy(
- finalPosition = fullyStretchedDimens.backgroundDimens.edgeCornerRadius,
- amount = edgeCornerStretchAmount
+ finalPosition = fullyStretchedDimens.backgroundDimens.edgeCornerRadius,
+ amount = edgeCornerStretchAmount
)
backgroundFarCornerRadius.stretchBy(
- finalPosition = fullyStretchedDimens.backgroundDimens.farCornerRadius,
- amount = farCornerStretchAmount
+ finalPosition = fullyStretchedDimens.backgroundDimens.farCornerRadius,
+ amount = farCornerStretchAmount
)
}
@@ -373,8 +378,11 @@
}
fun popArrowAlpha(startingVelocity: Float, springForce: SpringForce? = null) {
- arrowAlpha.stretchTo(stretchAmount = 0f, startingVelocity = startingVelocity,
- springForce = springForce)
+ arrowAlpha.stretchTo(
+ stretchAmount = 0f,
+ startingVelocity = startingVelocity,
+ springForce = springForce
+ )
}
fun resetStretch() {
@@ -392,12 +400,10 @@
backgroundFarCornerRadius.snapToRestingPosition()
}
- /**
- * Updates resting arrow and background size not accounting for stretch
- */
+ /** Updates resting arrow and background size not accounting for stretch */
internal fun setRestingDimens(
- restingParams: EdgePanelParams.BackIndicatorDimens,
- animate: Boolean = true
+ restingParams: EdgePanelParams.BackIndicatorDimens,
+ animate: Boolean = true
) {
horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation)
scale.updateRestingPosition(restingParams.scale)
@@ -410,27 +416,29 @@
backgroundWidth.updateRestingPosition(restingParams.backgroundDimens.width, animate)
backgroundHeight.updateRestingPosition(restingParams.backgroundDimens.height, animate)
backgroundEdgeCornerRadius.updateRestingPosition(
- restingParams.backgroundDimens.edgeCornerRadius, animate
+ restingParams.backgroundDimens.edgeCornerRadius,
+ animate
)
backgroundFarCornerRadius.updateRestingPosition(
- restingParams.backgroundDimens.farCornerRadius, animate
+ restingParams.backgroundDimens.farCornerRadius,
+ animate
)
}
fun animateVertically(yPos: Float) = verticalTranslation.stretchTo(yPos)
fun setSpring(
- horizontalTranslation: SpringForce? = null,
- verticalTranslation: SpringForce? = null,
- scale: SpringForce? = null,
- arrowLength: SpringForce? = null,
- arrowHeight: SpringForce? = null,
- arrowAlpha: SpringForce? = null,
- backgroundAlpha: SpringForce? = null,
- backgroundFarCornerRadius: SpringForce? = null,
- backgroundEdgeCornerRadius: SpringForce? = null,
- backgroundWidth: SpringForce? = null,
- backgroundHeight: SpringForce? = null,
+ horizontalTranslation: SpringForce? = null,
+ verticalTranslation: SpringForce? = null,
+ scale: SpringForce? = null,
+ arrowLength: SpringForce? = null,
+ arrowHeight: SpringForce? = null,
+ arrowAlpha: SpringForce? = null,
+ backgroundAlpha: SpringForce? = null,
+ backgroundFarCornerRadius: SpringForce? = null,
+ backgroundEdgeCornerRadius: SpringForce? = null,
+ backgroundWidth: SpringForce? = null,
+ backgroundHeight: SpringForce? = null,
) {
arrowLength?.let { this.arrowLength.spring = it }
arrowHeight?.let { this.arrowHeight.spring = it }
@@ -459,26 +467,28 @@
if (!isLeftPanel) canvas.scale(-1f, 1f, canvasWidth / 2.0f, 0f)
- canvas.translate(
- horizontalTranslation.pos,
- height * 0.5f + verticalTranslation.pos
- )
+ canvas.translate(horizontalTranslation.pos, height * 0.5f + verticalTranslation.pos)
canvas.scale(scale.pos, scale.pos, scalePivotX, 0f)
- val arrowBackground = arrowBackgroundRect.apply {
- left = 0f
- top = -halfHeight
- right = backgroundWidth
- bottom = halfHeight
- }.toPathWithRoundCorners(
- topLeft = edgeCorner,
- bottomLeft = edgeCorner,
- topRight = farCorner,
- bottomRight = farCorner
+ val arrowBackground =
+ arrowBackgroundRect
+ .apply {
+ left = 0f
+ top = -halfHeight
+ right = backgroundWidth
+ bottom = halfHeight
+ }
+ .toPathWithRoundCorners(
+ topLeft = edgeCorner,
+ bottomLeft = edgeCorner,
+ topRight = farCorner,
+ bottomRight = farCorner
+ )
+ canvas.drawPath(
+ arrowBackground,
+ arrowBackgroundPaint.apply { alpha = (255 * backgroundAlpha.pos).toInt() }
)
- canvas.drawPath(arrowBackground,
- arrowBackgroundPaint.apply { alpha = (255 * backgroundAlpha.pos).toInt() })
val dx = arrowLength.pos
val dy = arrowHeight.pos
@@ -487,8 +497,8 @@
// either the tip or the back of the arrow, whichever is closer
val arrowOffset = (backgroundWidth - dx) / 2
canvas.translate(
- /* dx= */ arrowOffset,
- /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */
+ /* dx= */ arrowOffset,
+ /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */
)
val arrowPointsAwayFromEdge = !arrowsPointLeft.xor(isLeftPanel)
@@ -500,8 +510,8 @@
}
val arrowPath = calculateArrowPath(dx = dx, dy = dy)
- val arrowPaint = arrowPaint
- .apply { alpha = (255 * min(arrowAlpha.pos, backgroundAlpha.pos)).toInt() }
+ val arrowPaint =
+ arrowPaint.apply { alpha = (255 * min(arrowAlpha.pos, backgroundAlpha.pos)).toInt() }
canvas.drawPath(arrowPath, arrowPaint)
canvas.restore()
@@ -519,17 +529,23 @@
}
private fun RectF.toPathWithRoundCorners(
- topLeft: Float = 0f,
- topRight: Float = 0f,
- bottomRight: Float = 0f,
- bottomLeft: Float = 0f
- ): Path = Path().apply {
- val corners = floatArrayOf(
- topLeft, topLeft,
- topRight, topRight,
- bottomRight, bottomRight,
- bottomLeft, bottomLeft
- )
- addRoundRect(this@toPathWithRoundCorners, corners, Path.Direction.CW)
- }
-}
\ No newline at end of file
+ topLeft: Float = 0f,
+ topRight: Float = 0f,
+ bottomRight: Float = 0f,
+ bottomLeft: Float = 0f
+ ): Path =
+ Path().apply {
+ val corners =
+ floatArrayOf(
+ topLeft,
+ topLeft,
+ topRight,
+ topRight,
+ bottomRight,
+ bottomRight,
+ bottomLeft,
+ bottomLeft
+ )
+ addRoundRect(this@toPathWithRoundCorners, corners, Path.Direction.CW)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
index f8086f5..18358a7 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -27,7 +27,6 @@
import android.view.HapticFeedbackConstants
import android.view.MotionEvent
import android.view.VelocityTracker
-import android.view.View
import android.view.ViewConfiguration
import android.view.WindowManager
import androidx.annotation.VisibleForTesting
@@ -37,11 +36,12 @@
import com.android.internal.jank.Cuj
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.util.LatencyTracker
-import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.NavigationEdgeBackPlugin
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.ViewController
+import com.android.systemui.util.concurrency.BackPanelUiThread
+import com.android.systemui.util.concurrency.UiThreadContext
import com.android.systemui.util.time.SystemClock
import java.io.PrintWriter
import javax.inject.Inject
@@ -85,11 +85,11 @@
context: Context,
private val windowManager: WindowManager,
private val viewConfiguration: ViewConfiguration,
- @Main private val mainHandler: Handler,
+ private val mainHandler: Handler,
private val systemClock: SystemClock,
private val vibratorHelper: VibratorHelper,
private val configurationController: ConfigurationController,
- private val latencyTracker: LatencyTracker,
+ latencyTracker: LatencyTracker,
private val interactionJankMonitor: InteractionJankMonitor,
) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin {
@@ -104,7 +104,7 @@
constructor(
private val windowManager: WindowManager,
private val viewConfiguration: ViewConfiguration,
- @Main private val mainHandler: Handler,
+ @BackPanelUiThread private val uiThreadContext: UiThreadContext,
private val systemClock: SystemClock,
private val vibratorHelper: VibratorHelper,
private val configurationController: ConfigurationController,
@@ -113,20 +113,19 @@
) {
/** Construct a [BackPanelController]. */
fun create(context: Context): BackPanelController {
- val backPanelController =
- BackPanelController(
+ uiThreadContext.isCurrentThread()
+ return BackPanelController(
context,
windowManager,
viewConfiguration,
- mainHandler,
+ uiThreadContext.handler,
systemClock,
vibratorHelper,
configurationController,
latencyTracker,
interactionJankMonitor
)
- backPanelController.init()
- return backPanelController
+ .also { it.init() }
}
}
@@ -164,6 +163,7 @@
private val elapsedTimeSinceInactive
get() = systemClock.uptimeMillis() - gestureInactiveTime
+
private val elapsedTimeSinceEntry
get() = systemClock.uptimeMillis() - gestureEntryTime
@@ -612,6 +612,7 @@
}
private var previousPreThresholdWidthInterpolator = params.entryWidthInterpolator
+
private fun preThresholdWidthStretchAmount(progress: Float): Float {
val interpolator = run {
val isPastSlop = totalTouchDeltaInactive > viewConfiguration.scaledTouchSlop
@@ -677,8 +678,7 @@
velocityTracker?.run {
computeCurrentVelocity(PX_PER_SEC)
xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1)
- }
- ?: 0f
+ } ?: 0f
val isPastFlingVelocityThreshold =
flingVelocity > viewConfiguration.scaledMinimumFlingVelocity
return flingDistance > minFlingDistance && isPastFlingVelocityThreshold
@@ -1006,15 +1006,15 @@
private fun performDeactivatedHapticFeedback() {
vibratorHelper.performHapticFeedback(
- mView,
- HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
+ mView,
+ HapticFeedbackConstants.GESTURE_THRESHOLD_DEACTIVATE
)
}
private fun performActivatedHapticFeedback() {
vibratorHelper.performHapticFeedback(
- mView,
- HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
+ mView,
+ HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE
)
}
@@ -1028,8 +1028,7 @@
velocityTracker?.run {
computeCurrentVelocity(PX_PER_MS)
MathUtils.smoothStep(slowVelocityBound, fastVelocityBound, abs(xVelocity))
- }
- ?: valueOnFastVelocity
+ } ?: valueOnFastVelocity
return MathUtils.lerp(valueOnFastVelocity, valueOnSlowVelocity, 1 - factor)
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index d0f8412..41cd2c4 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -44,8 +44,6 @@
import android.graphics.Region;
import android.hardware.input.InputManager;
import android.icu.text.SimpleDateFormat;
-import android.os.Handler;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
@@ -55,7 +53,6 @@
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.TypedValue;
-import android.view.Choreographer;
import android.view.ISystemGestureExclusionListener;
import android.view.IWindowManager;
import android.view.InputDevice;
@@ -75,7 +72,6 @@
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.FalsingManager;
@@ -94,7 +90,8 @@
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import com.android.systemui.statusbar.phone.LightBarController;
-import com.android.systemui.util.Assert;
+import com.android.systemui.util.concurrency.BackPanelUiThread;
+import com.android.systemui.util.concurrency.UiThreadContext;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.desktopmode.DesktopMode;
import com.android.wm.shell.pip.Pip;
@@ -136,7 +133,7 @@
public void onSystemGestureExclusionChanged(int displayId,
Region systemGestureExclusion, Region unrestrictedOrNull) {
if (displayId == mDisplayId) {
- mMainExecutor.execute(() -> {
+ mUiThreadContext.getExecutor().execute(() -> {
mExcludeRegion.set(systemGestureExclusion);
mUnrestrictedExcludeRegion.set(unrestrictedOrNull != null
? unrestrictedOrNull : systemGestureExclusion);
@@ -215,8 +212,7 @@
private final Point mDisplaySize = new Point();
private final int mDisplayId;
- private final Executor mMainExecutor;
- private final Handler mMainHandler;
+ private final UiThreadContext mUiThreadContext;
private final Executor mBackgroundExecutor;
private final Rect mPipExcludedBounds = new Rect();
@@ -411,8 +407,7 @@
OverviewProxyService overviewProxyService,
SysUiState sysUiState,
PluginManager pluginManager,
- @Main Executor executor,
- @Main Handler handler,
+ @BackPanelUiThread UiThreadContext uiThreadContext,
@Background Executor backgroundExecutor,
UserTracker userTracker,
NavigationModeController navigationModeController,
@@ -428,8 +423,7 @@
Provider<LightBarController> lightBarControllerProvider) {
mContext = context;
mDisplayId = context.getDisplayId();
- mMainExecutor = executor;
- mMainHandler = handler;
+ mUiThreadContext = uiThreadContext;
mBackgroundExecutor = backgroundExecutor;
mUserTracker = userTracker;
mOverviewProxyService = overviewProxyService;
@@ -478,7 +472,7 @@
ViewConfiguration.getLongPressTimeout());
mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
- mMainHandler, mContext, this::onNavigationSettingsChanged);
+ mUiThreadContext.getHandler(), mContext, this::onNavigationSettingsChanged);
updateCurrentUserResources();
}
@@ -506,11 +500,13 @@
final boolean previousForcedVisible = mIsButtonForcedVisible;
mIsButtonForcedVisible =
mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
+ // Update this before calling mButtonForcedVisibleCallback since NavigationBar will relayout
+ // and query isHandlingGestures() as a part of the callback
+ mIsBackGestureAllowed = !mIsButtonForcedVisible;
if (previousForcedVisible != mIsButtonForcedVisible
&& mButtonForcedVisibleCallback != null) {
mButtonForcedVisibleCallback.accept(mIsButtonForcedVisible);
}
- mIsBackGestureAllowed = !mIsButtonForcedVisible;
final DisplayMetrics dm = res.getDisplayMetrics();
final float defaultGestureHeight = res.getDimension(
@@ -564,13 +560,15 @@
mIsAttached = true;
mOverviewProxyService.addCallback(mQuickSwitchListener);
mSysUiState.addCallback(mSysUiStateCallback);
- mInputManager.registerInputDeviceListener(mInputDeviceListener, mMainHandler);
- int [] inputDevices = mInputManager.getInputDeviceIds();
+ mInputManager.registerInputDeviceListener(
+ mInputDeviceListener,
+ mUiThreadContext.getHandler());
+ int[] inputDevices = mInputManager.getInputDeviceIds();
for (int inputDeviceId : inputDevices) {
mInputDeviceListener.onInputDeviceAdded(inputDeviceId);
}
updateIsEnabled();
- mUserTracker.addCallback(mUserChangedCallback, mMainExecutor);
+ mUserTracker.addCallback(mUserChangedCallback, mUiThreadContext.getExecutor());
}
/**
@@ -617,6 +615,10 @@
}
private void updateIsEnabled() {
+ mUiThreadContext.runWithScissors(this::updateIsEnabledInner);
+ }
+
+ private void updateIsEnabledInner() {
try {
Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled");
@@ -661,12 +663,12 @@
TaskStackChangeListeners.getInstance().registerTaskStackListener(
mTaskStackListener);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
- mMainExecutor::execute, mOnPropertiesChangedListener);
+ mUiThreadContext.getExecutor()::execute, mOnPropertiesChangedListener);
mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(
mOnIsInPipStateChangedListener));
mDesktopModeOptional.ifPresent(
dm -> dm.addDesktopGestureExclusionRegionListener(
- mDesktopCornersChangedListener, mMainExecutor));
+ mDesktopCornersChangedListener, mUiThreadContext.getExecutor()));
try {
mWindowManagerService.registerSystemGestureExclusionListener(
@@ -677,8 +679,8 @@
// Register input event receiver
mInputMonitor = new InputMonitorCompat("edge-swipe", mDisplayId);
- mInputEventReceiver = mInputMonitor.getInputReceiver(Looper.getMainLooper(),
- Choreographer.getInstance(), this::onInputEvent);
+ mInputEventReceiver = mInputMonitor.getInputReceiver(mUiThreadContext.getLooper(),
+ mUiThreadContext.getChoreographer(), this::onInputEvent);
// Add a nav bar panel window
resetEdgeBackPlugin();
@@ -773,7 +775,7 @@
mUseMLModel = newState;
if (mUseMLModel) {
- Assert.isMainThread();
+ mUiThreadContext.isCurrentThread();
if (mMLModelIsLoading) {
Log.d(TAG, "Model tried to load while already loading.");
return;
@@ -804,12 +806,13 @@
}
BackGestureTfClassifierProvider finalProvider = provider;
Map<String, Integer> finalVocab = vocab;
- mMainExecutor.execute(() -> onMLModelLoadFinished(finalProvider, finalVocab, threshold));
+ mUiThreadContext.getExecutor().execute(
+ () -> onMLModelLoadFinished(finalProvider, finalVocab, threshold));
}
private void onMLModelLoadFinished(BackGestureTfClassifierProvider provider,
Map<String, Integer> vocab, float threshold) {
- Assert.isMainThread();
+ mUiThreadContext.isCurrentThread();
mMLModelIsLoading = false;
if (!mUseMLModel) {
// This can happen if the user disables Gesture Nav while the model is loading.
@@ -1291,7 +1294,7 @@
updateBackAnimationThresholds();
if (mLightBarControllerProvider.get() != null) {
mBackAnimation.setStatusBarCustomizer((appearance) -> {
- mMainExecutor.execute(() ->
+ mUiThreadContext.getExecutor().execute(() ->
mLightBarControllerProvider.get()
.customizeStatusBarAppearance(appearance));
});
@@ -1308,8 +1311,7 @@
private final OverviewProxyService mOverviewProxyService;
private final SysUiState mSysUiState;
private final PluginManager mPluginManager;
- private final Executor mExecutor;
- private final Handler mHandler;
+ private final UiThreadContext mUiThreadContext;
private final Executor mBackgroundExecutor;
private final UserTracker mUserTracker;
private final NavigationModeController mNavigationModeController;
@@ -1327,29 +1329,27 @@
@Inject
public Factory(OverviewProxyService overviewProxyService,
- SysUiState sysUiState,
- PluginManager pluginManager,
- @Main Executor executor,
- @Main Handler handler,
- @Background Executor backgroundExecutor,
- UserTracker userTracker,
- NavigationModeController navigationModeController,
- BackPanelController.Factory backPanelControllerFactory,
- ViewConfiguration viewConfiguration,
- WindowManager windowManager,
- IWindowManager windowManagerService,
- InputManager inputManager,
- Optional<Pip> pipOptional,
- Optional<DesktopMode> desktopModeOptional,
- FalsingManager falsingManager,
- Provider<BackGestureTfClassifierProvider>
- backGestureTfClassifierProviderProvider,
- Provider<LightBarController> lightBarControllerProvider) {
+ SysUiState sysUiState,
+ PluginManager pluginManager,
+ @BackPanelUiThread UiThreadContext uiThreadContext,
+ @Background Executor backgroundExecutor,
+ UserTracker userTracker,
+ NavigationModeController navigationModeController,
+ BackPanelController.Factory backPanelControllerFactory,
+ ViewConfiguration viewConfiguration,
+ WindowManager windowManager,
+ IWindowManager windowManagerService,
+ InputManager inputManager,
+ Optional<Pip> pipOptional,
+ Optional<DesktopMode> desktopModeOptional,
+ FalsingManager falsingManager,
+ Provider<BackGestureTfClassifierProvider>
+ backGestureTfClassifierProviderProvider,
+ Provider<LightBarController> lightBarControllerProvider) {
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
- mExecutor = executor;
- mHandler = handler;
+ mUiThreadContext = uiThreadContext;
mBackgroundExecutor = backgroundExecutor;
mUserTracker = userTracker;
mNavigationModeController = navigationModeController;
@@ -1367,26 +1367,26 @@
/** Construct a {@link EdgeBackGestureHandler}. */
public EdgeBackGestureHandler create(Context context) {
- return new EdgeBackGestureHandler(
- context,
- mOverviewProxyService,
- mSysUiState,
- mPluginManager,
- mExecutor,
- mHandler,
- mBackgroundExecutor,
- mUserTracker,
- mNavigationModeController,
- mBackPanelControllerFactory,
- mViewConfiguration,
- mWindowManager,
- mWindowManagerService,
- mInputManager,
- mPipOptional,
- mDesktopModeOptional,
- mFalsingManager,
- mBackGestureTfClassifierProviderProvider,
- mLightBarControllerProvider);
+ return mUiThreadContext.runWithScissors(
+ () -> new EdgeBackGestureHandler(
+ context,
+ mOverviewProxyService,
+ mSysUiState,
+ mPluginManager,
+ mUiThreadContext,
+ mBackgroundExecutor,
+ mUserTracker,
+ mNavigationModeController,
+ mBackPanelControllerFactory,
+ mViewConfiguration,
+ mWindowManager,
+ mWindowManagerService,
+ mInputManager,
+ mPipOptional,
+ mDesktopModeOptional,
+ mFalsingManager,
+ mBackGestureTfClassifierProviderProvider,
+ mLightBarControllerProvider));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
index 439b7e1..db8749f 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -10,92 +10,114 @@
data class EdgePanelParams(private var resources: Resources) {
data class ArrowDimens(
- val length: Float? = 0f,
- val height: Float? = 0f,
- val alpha: Float = 0f,
- val heightSpring: SpringForce? = null,
- val lengthSpring: SpringForce? = null,
- var alphaSpring: Step<SpringForce>? = null,
- var alphaInterpolator: Step<Float>? = null
+ val length: Float? = 0f,
+ val height: Float? = 0f,
+ val alpha: Float = 0f,
+ val heightSpring: SpringForce? = null,
+ val lengthSpring: SpringForce? = null,
+ var alphaSpring: Step<SpringForce>? = null,
+ var alphaInterpolator: Step<Float>? = null
)
data class BackgroundDimens(
- val width: Float? = 0f,
- val height: Float = 0f,
- val edgeCornerRadius: Float = 0f,
- val farCornerRadius: Float = 0f,
- val alpha: Float = 0f,
- val widthSpring: SpringForce? = null,
- val heightSpring: SpringForce? = null,
- val farCornerRadiusSpring: SpringForce? = null,
- val edgeCornerRadiusSpring: SpringForce? = null,
- val alphaSpring: SpringForce? = null,
+ val width: Float? = 0f,
+ val height: Float = 0f,
+ val edgeCornerRadius: Float = 0f,
+ val farCornerRadius: Float = 0f,
+ val alpha: Float = 0f,
+ val widthSpring: SpringForce? = null,
+ val heightSpring: SpringForce? = null,
+ val farCornerRadiusSpring: SpringForce? = null,
+ val edgeCornerRadiusSpring: SpringForce? = null,
+ val alphaSpring: SpringForce? = null,
)
data class BackIndicatorDimens(
- val horizontalTranslation: Float? = 0f,
- val scale: Float = 0f,
- val scalePivotX: Float? = null,
- val arrowDimens: ArrowDimens,
- val backgroundDimens: BackgroundDimens,
- val verticalTranslationSpring: SpringForce? = null,
- val horizontalTranslationSpring: SpringForce? = null,
- val scaleSpring: SpringForce? = null,
+ val horizontalTranslation: Float? = 0f,
+ val scale: Float = 0f,
+ val scalePivotX: Float? = null,
+ val arrowDimens: ArrowDimens,
+ val backgroundDimens: BackgroundDimens,
+ val verticalTranslationSpring: SpringForce? = null,
+ val horizontalTranslationSpring: SpringForce? = null,
+ val scaleSpring: SpringForce? = null,
)
lateinit var entryIndicator: BackIndicatorDimens
private set
+
lateinit var activeIndicator: BackIndicatorDimens
private set
+
lateinit var cancelledIndicator: BackIndicatorDimens
private set
+
lateinit var flungIndicator: BackIndicatorDimens
private set
+
lateinit var committedIndicator: BackIndicatorDimens
private set
+
lateinit var preThresholdIndicator: BackIndicatorDimens
private set
+
lateinit var fullyStretchedIndicator: BackIndicatorDimens
private set
// navigation bar edge constants
var arrowPaddingEnd: Int = 0
private set
+
var arrowThickness: Float = 0f
private set
+
// The closest to y
var minArrowYPosition: Int = 0
private set
+
var fingerOffset: Int = 0
private set
+
var staticTriggerThreshold: Float = 0f
private set
+
var reactivationTriggerThreshold: Float = 0f
private set
+
var deactivationTriggerThreshold: Float = 0f
get() = -field
private set
+
lateinit var dynamicTriggerThresholdRange: ClosedRange<Float>
private set
+
var swipeProgressThreshold: Float = 0f
private set
lateinit var entryWidthInterpolator: Interpolator
private set
+
lateinit var entryWidthTowardsEdgeInterpolator: Interpolator
private set
+
lateinit var activeWidthInterpolator: Interpolator
private set
+
lateinit var arrowAngleInterpolator: Interpolator
private set
+
lateinit var horizontalTranslationInterpolator: Interpolator
private set
+
lateinit var verticalTranslationInterpolator: Interpolator
private set
+
lateinit var farCornerInterpolator: Interpolator
private set
+
lateinit var edgeCornerInterpolator: Interpolator
private set
+
lateinit var heightInterpolator: Interpolator
private set
@@ -108,7 +130,10 @@
}
private fun getDimenFloat(id: Int): Float {
- return TypedValue().run { resources.getValue(id, this, true); float }
+ return TypedValue().run {
+ resources.getValue(id, this, true)
+ float
+ }
}
private fun getPx(id: Int): Int {
@@ -123,11 +148,10 @@
fingerOffset = getPx(R.dimen.navigation_edge_finger_offset)
staticTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold)
reactivationTriggerThreshold =
- getDimen(R.dimen.navigation_edge_action_reactivation_drag_threshold)
+ getDimen(R.dimen.navigation_edge_action_reactivation_drag_threshold)
deactivationTriggerThreshold =
- getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold)
- dynamicTriggerThresholdRange =
- reactivationTriggerThreshold..deactivationTriggerThreshold
+ getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold)
+ dynamicTriggerThresholdRange = reactivationTriggerThreshold..deactivationTriggerThreshold
swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold)
entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f)
@@ -149,27 +173,31 @@
val commonArrowDimensAlphaThreshold = .165f
val commonArrowDimensAlphaFactor = 1.05f
- val commonArrowDimensAlphaSpring = Step(
- threshold = commonArrowDimensAlphaThreshold,
- factor = commonArrowDimensAlphaFactor,
- postThreshold = createSpring(180f, 0.9f),
- preThreshold = createSpring(2000f, 0.6f)
- )
- val commonArrowDimensAlphaSpringInterpolator = Step(
- threshold = commonArrowDimensAlphaThreshold,
- factor = commonArrowDimensAlphaFactor,
- postThreshold = 1f,
- preThreshold = 0f
- )
+ val commonArrowDimensAlphaSpring =
+ Step(
+ threshold = commonArrowDimensAlphaThreshold,
+ factor = commonArrowDimensAlphaFactor,
+ postThreshold = createSpring(180f, 0.9f),
+ preThreshold = createSpring(2000f, 0.6f)
+ )
+ val commonArrowDimensAlphaSpringInterpolator =
+ Step(
+ threshold = commonArrowDimensAlphaThreshold,
+ factor = commonArrowDimensAlphaFactor,
+ postThreshold = 1f,
+ preThreshold = 0f
+ )
- entryIndicator = BackIndicatorDimens(
+ entryIndicator =
+ BackIndicatorDimens(
horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin),
scale = getDimenFloat(R.dimen.navigation_edge_entry_scale),
scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
horizontalTranslationSpring = createSpring(800f, 0.76f),
verticalTranslationSpring = createSpring(30000f, 1f),
scaleSpring = createSpring(120f, 0.8f),
- arrowDimens = ArrowDimens(
+ arrowDimens =
+ ArrowDimens(
length = getDimen(R.dimen.navigation_edge_entry_arrow_length),
height = getDimen(R.dimen.navigation_edge_entry_arrow_height),
alpha = 0f,
@@ -177,8 +205,9 @@
heightSpring = createSpring(600f, 0.4f),
alphaSpring = commonArrowDimensAlphaSpring,
alphaInterpolator = commonArrowDimensAlphaSpringInterpolator
- ),
- backgroundDimens = BackgroundDimens(
+ ),
+ backgroundDimens =
+ BackgroundDimens(
alpha = 1f,
width = getDimen(R.dimen.navigation_edge_entry_background_width),
height = getDimen(R.dimen.navigation_edge_entry_background_height),
@@ -188,16 +217,18 @@
heightSpring = createSpring(1500f, 0.45f),
farCornerRadiusSpring = createSpring(300f, 0.5f),
edgeCornerRadiusSpring = createSpring(150f, 0.5f),
- )
- )
+ )
+ )
- activeIndicator = BackIndicatorDimens(
+ activeIndicator =
+ BackIndicatorDimens(
horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
scale = getDimenFloat(R.dimen.navigation_edge_active_scale),
horizontalTranslationSpring = createSpring(1000f, 0.8f),
scaleSpring = createSpring(325f, 0.55f),
scalePivotX = getDimen(R.dimen.navigation_edge_active_background_width),
- arrowDimens = ArrowDimens(
+ arrowDimens =
+ ArrowDimens(
length = getDimen(R.dimen.navigation_edge_active_arrow_length),
height = getDimen(R.dimen.navigation_edge_active_arrow_height),
alpha = 1f,
@@ -205,8 +236,9 @@
heightSpring = activeCommittedArrowHeightSpring,
alphaSpring = commonArrowDimensAlphaSpring,
alphaInterpolator = commonArrowDimensAlphaSpringInterpolator
- ),
- backgroundDimens = BackgroundDimens(
+ ),
+ backgroundDimens =
+ BackgroundDimens(
alpha = 1f,
width = getDimen(R.dimen.navigation_edge_active_background_width),
height = getDimen(R.dimen.navigation_edge_active_background_height),
@@ -216,16 +248,18 @@
heightSpring = createSpring(10000f, 1f),
edgeCornerRadiusSpring = createSpring(2600f, 0.855f),
farCornerRadiusSpring = createSpring(1200f, 0.30f),
- )
- )
+ )
+ )
- preThresholdIndicator = BackIndicatorDimens(
+ preThresholdIndicator =
+ BackIndicatorDimens(
horizontalTranslation = getDimen(R.dimen.navigation_edge_pre_threshold_margin),
scale = getDimenFloat(R.dimen.navigation_edge_pre_threshold_scale),
scalePivotX = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
scaleSpring = createSpring(120f, 0.8f),
horizontalTranslationSpring = createSpring(6000f, 1f),
- arrowDimens = ArrowDimens(
+ arrowDimens =
+ ArrowDimens(
length = getDimen(R.dimen.navigation_edge_pre_threshold_arrow_length),
height = getDimen(R.dimen.navigation_edge_pre_threshold_arrow_height),
alpha = 1f,
@@ -233,32 +267,36 @@
heightSpring = createSpring(100f, 0.6f),
alphaSpring = commonArrowDimensAlphaSpring,
alphaInterpolator = commonArrowDimensAlphaSpringInterpolator
- ),
- backgroundDimens = BackgroundDimens(
+ ),
+ backgroundDimens =
+ BackgroundDimens(
alpha = 1f,
width = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
height = getDimen(R.dimen.navigation_edge_pre_threshold_background_height),
edgeCornerRadius =
- getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
+ getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
farCornerRadius =
- getDimen(R.dimen.navigation_edge_pre_threshold_far_corners),
+ getDimen(R.dimen.navigation_edge_pre_threshold_far_corners),
widthSpring = createSpring(650f, 1f),
heightSpring = createSpring(1500f, 0.45f),
farCornerRadiusSpring = createSpring(300f, 1f),
edgeCornerRadiusSpring = createSpring(250f, 0.5f),
- )
- )
+ )
+ )
- committedIndicator = activeIndicator.copy(
+ committedIndicator =
+ activeIndicator.copy(
horizontalTranslation = null,
scalePivotX = null,
- arrowDimens = activeIndicator.arrowDimens.copy(
+ arrowDimens =
+ activeIndicator.arrowDimens.copy(
lengthSpring = activeCommittedArrowLengthSpring,
heightSpring = activeCommittedArrowHeightSpring,
length = null,
height = null,
- ),
- backgroundDimens = activeIndicator.backgroundDimens.copy(
+ ),
+ backgroundDimens =
+ activeIndicator.backgroundDimens.copy(
alpha = 0f,
// explicitly set to null to preserve previous width upon state change
width = null,
@@ -267,49 +305,57 @@
edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring,
farCornerRadiusSpring = flungCommittedFarCornerSpring,
alphaSpring = createSpring(1400f, 1f),
- ),
+ ),
scale = 0.86f,
scaleSpring = createSpring(5700f, 1f),
- )
+ )
- flungIndicator = committedIndicator.copy(
- arrowDimens = committedIndicator.arrowDimens.copy(
+ flungIndicator =
+ committedIndicator.copy(
+ arrowDimens =
+ committedIndicator.arrowDimens.copy(
lengthSpring = createSpring(850f, 0.46f),
heightSpring = createSpring(850f, 0.46f),
length = activeIndicator.arrowDimens.length,
height = activeIndicator.arrowDimens.height
- ),
- backgroundDimens = committedIndicator.backgroundDimens.copy(
+ ),
+ backgroundDimens =
+ committedIndicator.backgroundDimens.copy(
widthSpring = flungCommittedWidthSpring,
heightSpring = flungCommittedHeightSpring,
edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring,
farCornerRadiusSpring = flungCommittedFarCornerSpring,
- )
- )
+ )
+ )
- cancelledIndicator = entryIndicator.copy(
- backgroundDimens = entryIndicator.backgroundDimens.copy(
+ cancelledIndicator =
+ entryIndicator.copy(
+ backgroundDimens =
+ entryIndicator.backgroundDimens.copy(
width = 0f,
alpha = 0f,
alphaSpring = createSpring(450f, 1f)
- )
- )
+ )
+ )
- fullyStretchedIndicator = BackIndicatorDimens(
+ fullyStretchedIndicator =
+ BackIndicatorDimens(
horizontalTranslation = getDimen(R.dimen.navigation_edge_stretch_margin),
scale = getDimenFloat(R.dimen.navigation_edge_stretch_scale),
horizontalTranslationSpring = null,
verticalTranslationSpring = null,
scaleSpring = null,
- arrowDimens = ArrowDimens(
+ arrowDimens =
+ ArrowDimens(
length = getDimen(R.dimen.navigation_edge_stretched_arrow_length),
height = getDimen(R.dimen.navigation_edge_stretched_arrow_height),
alpha = 1f,
alphaSpring = null,
heightSpring = null,
lengthSpring = null,
- ),
- backgroundDimens = BackgroundDimens(
+ ),
+ backgroundDimens =
+ BackgroundDimens(
alpha = 1f,
width = getDimen(R.dimen.navigation_edge_stretch_background_width),
height = getDimen(R.dimen.navigation_edge_stretch_background_height),
@@ -320,11 +366,11 @@
heightSpring = null,
edgeCornerRadiusSpring = null,
farCornerRadiusSpring = null,
- )
- )
+ )
+ )
}
}
fun createSpring(stiffness: Float, dampingRatio: Float): SpringForce {
return SpringForce().setStiffness(stiffness).setDampingRatio(dampingRatio)
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt
index deb0fed..954e94a 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.kt
@@ -26,11 +26,7 @@
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.theme.PlatformTheme
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import com.android.systemui.people.ui.compose.PeopleScreen
-import com.android.systemui.people.ui.view.PeopleViewBinder
-import com.android.systemui.people.ui.view.PeopleViewBinder.bind
import com.android.systemui.people.ui.viewmodel.PeopleViewModel
import javax.inject.Inject
import kotlinx.coroutines.launch
@@ -38,10 +34,7 @@
/** People Tile Widget configuration activity that shows the user their conversation tiles. */
class PeopleSpaceActivity
@Inject
-constructor(
- private val viewModelFactory: PeopleViewModel.Factory,
- private val featureFlags: FeatureFlags,
-) : ComponentActivity() {
+constructor(private val viewModelFactory: PeopleViewModel.Factory) : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setResult(RESULT_CANCELED)
@@ -66,17 +59,7 @@
}
// Set the content of the activity, using either the View or Compose implementation.
- if (featureFlags.isEnabled(Flags.COMPOSE_PEOPLE_SPACE)) {
- Log.d(TAG, "Using the Compose implementation of the PeopleSpaceActivity")
- setContent {
- PlatformTheme { PeopleScreen(viewModel, onResult = { finishActivity(it) }) }
- }
- } else {
- Log.d(TAG, "Using the View implementation of the PeopleSpaceActivity")
- val view = PeopleViewBinder.create(this)
- bind(view, viewModel, lifecycleOwner = this, onResult = { finishActivity(it) })
- setContentView(view)
- }
+ setContent { PlatformTheme { PeopleScreen(viewModel, onResult = { finishActivity(it) }) } }
}
private fun finishActivity(result: PeopleViewModel.Result) {
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
deleted file mode 100644
index 59c76ad..0000000
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceTileView.java
+++ /dev/null
@@ -1,81 +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.people;
-
-import android.app.people.PeopleSpaceTile;
-import android.content.Context;
-import android.content.pm.LauncherApps;
-import android.graphics.Bitmap;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.systemui.res.R;
-
-/**
- * PeopleSpaceTileView renders an individual person's tile with associated status.
- */
-public class PeopleSpaceTileView extends LinearLayout {
-
- private View mTileView;
- private TextView mNameView;
- private ImageView mPersonIconView;
-
- public PeopleSpaceTileView(Context context, ViewGroup view, String shortcutId, boolean isLast) {
- super(context);
- mTileView = view.findViewWithTag(shortcutId);
- if (mTileView == null) {
- LayoutInflater inflater = LayoutInflater.from(context);
- mTileView = inflater.inflate(R.layout.people_space_tile_view, view, false);
- view.addView(mTileView, LayoutParams.MATCH_PARENT,
- LayoutParams.MATCH_PARENT);
- mTileView.setTag(shortcutId);
-
- // If it's not the last conversation in this section, add a divider.
- if (!isLast) {
- inflater.inflate(R.layout.people_space_activity_list_divider, view, true);
- }
- }
- mNameView = mTileView.findViewById(R.id.tile_view_name);
- mPersonIconView = mTileView.findViewById(R.id.tile_view_person_icon);
- }
-
- /** Sets the name text on the tile. */
- public void setName(String name) {
- mNameView.setText(name);
- }
-
- /** Sets the person and package drawable on the tile. */
- public void setPersonIcon(Bitmap bitmap) {
- mPersonIconView.setImageBitmap(bitmap);
- }
-
- /** Sets the click listener of the tile. */
- public void setOnClickListener(LauncherApps launcherApps, PeopleSpaceTile tile) {
- mTileView.setOnClickListener(v ->
- launcherApps.startShortcut(tile.getPackageName(), tile.getId(), null, null,
- tile.getUserHandle()));
- }
-
- /** Sets the click listener of the tile directly. */
- public void setOnClickListener(OnClickListener onClickListener) {
- mTileView.setOnClickListener(onClickListener);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
deleted file mode 100644
index 10a2b3c..0000000
--- a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.people.ui.view
-
-import android.content.Context
-import android.graphics.Color
-import android.graphics.Outline
-import android.graphics.drawable.GradientDrawable
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.view.ViewOutlineProvider
-import android.widget.LinearLayout
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.Lifecycle.State.CREATED
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.people.PeopleSpaceTileView
-import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
-import com.android.systemui.people.ui.viewmodel.PeopleViewModel
-import com.android.systemui.res.R
-import kotlinx.coroutines.flow.collect
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.launch
-
-/** A ViewBinder for [PeopleViewModel]. */
-object PeopleViewBinder {
- private const val TAG = "PeopleViewBinder"
-
- /**
- * The [ViewOutlineProvider] used to clip the corner radius of the recent and priority lists.
- */
- private val ViewOutlineProvider =
- object : ViewOutlineProvider() {
- override fun getOutline(view: View, outline: Outline) {
- outline.setRoundRect(
- 0,
- 0,
- view.width,
- view.height,
- view.context.resources.getDimension(R.dimen.people_space_widget_radius),
- )
- }
- }
-
- /** Create a [View] that can later be [bound][bind] to a [PeopleViewModel]. */
- @JvmStatic
- fun create(context: Context): ViewGroup {
- return LayoutInflater.from(context)
- .inflate(R.layout.people_space_activity, /* root= */ null) as ViewGroup
- }
-
- /** Bind [view] to [viewModel]. */
- @JvmStatic
- fun bind(
- view: ViewGroup,
- viewModel: PeopleViewModel,
- lifecycleOwner: LifecycleOwner,
- onResult: (PeopleViewModel.Result) -> Unit,
- ) {
- // Call [onResult] as soon as a result is available.
- lifecycleOwner.lifecycleScope.launch {
- lifecycleOwner.repeatOnLifecycle(CREATED) {
- viewModel.result.collect { result ->
- if (result != null) {
- viewModel.clearResult()
- onResult(result)
- }
- }
- }
- }
-
- // Start collecting the UI data once the Activity is STARTED.
- lifecycleOwner.lifecycleScope.launch {
- lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
- combine(
- viewModel.priorityTiles,
- viewModel.recentTiles,
- ) { priority, recent ->
- priority to recent
- }
- .collect { (priorityTiles, recentTiles) ->
- if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) {
- setConversationsContent(
- view,
- priorityTiles,
- recentTiles,
- viewModel.onTileClicked,
- )
- } else {
- setNoConversationsContent(view, viewModel.onUserJourneyCancelled)
- }
- }
- }
- }
- }
-
- private fun setNoConversationsContent(view: ViewGroup, onGotItClicked: () -> Unit) {
- // This should never happen.
- if (view.childCount > 1) {
- error("view has ${view.childCount} children, it should have maximum 1")
- }
-
- // The static content for no conversations is already shown.
- if (view.findViewById<View>(R.id.top_level_no_conversations) != null) {
- return
- }
-
- // If we were showing the content with conversations earlier, remove it.
- if (view.childCount == 1) {
- view.removeViewAt(0)
- }
-
- val context = view.context
- val noConversationsView =
- LayoutInflater.from(context)
- .inflate(R.layout.people_space_activity_no_conversations, /* root= */ view)
-
- noConversationsView.requireViewById<View>(R.id.got_it_button).setOnClickListener {
- onGotItClicked()
- }
-
- // The Tile preview has colorBackground as its background. Change it so it's different than
- // the activity's background.
- val item = noConversationsView.requireViewById<LinearLayout>(android.R.id.background)
- val shape = item.background as GradientDrawable
- val ta =
- context.theme.obtainStyledAttributes(
- intArrayOf(com.android.internal.R.attr.colorSurface)
- )
- shape.setColor(ta.getColor(0, Color.WHITE))
- ta.recycle()
- }
-
- private fun setConversationsContent(
- view: ViewGroup,
- priorityTiles: List<PeopleTileViewModel>,
- recentTiles: List<PeopleTileViewModel>,
- onTileClicked: (PeopleTileViewModel) -> Unit,
- ) {
- // This should never happen.
- if (view.childCount > 1) {
- error("view has ${view.childCount} children, it should have maximum 1")
- }
-
- // Inflate the content with conversations, if it's not already.
- if (view.findViewById<View>(R.id.top_level_with_conversations) == null) {
- // If we were showing the content without conversations earlier, remove it.
- if (view.childCount == 1) {
- view.removeViewAt(0)
- }
-
- LayoutInflater.from(view.context)
- .inflate(R.layout.people_space_activity_with_conversations, /* root= */ view)
- }
-
- // TODO(b/193782241): Replace the NestedScrollView + 2x LinearLayout from this layout into a
- // single RecyclerView once this screen is tested by screenshot tests. Introduce a
- // PeopleSpaceTileViewBinder that will properly create and bind the View associated to a
- // PeopleSpaceTileViewModel (and remove the PeopleSpaceTileView class).
- val conversationsView = view.requireViewById<View>(R.id.top_level_with_conversations)
- setTileViews(
- conversationsView,
- R.id.priority,
- R.id.priority_tiles,
- priorityTiles,
- onTileClicked,
- )
-
- setTileViews(
- conversationsView,
- R.id.recent,
- R.id.recent_tiles,
- recentTiles,
- onTileClicked,
- )
- }
-
- /** Sets a [PeopleSpaceTileView]s for each conversation. */
- private fun setTileViews(
- root: View,
- tilesListId: Int,
- tilesId: Int,
- tiles: List<PeopleTileViewModel>,
- onTileClicked: (PeopleTileViewModel) -> Unit,
- ) {
- // Remove any previously added tile.
- // TODO(b/193782241): Once this list is a big RecyclerView, set the current list and use
- // DiffUtil to do as less addView/removeView as possible.
- val layout = root.requireViewById<ViewGroup>(tilesId)
- layout.removeAllViews()
- layout.outlineProvider = ViewOutlineProvider
-
- val tilesListView = root.requireViewById<LinearLayout>(tilesListId)
- if (tiles.isEmpty()) {
- tilesListView.visibility = View.GONE
- return
- }
- tilesListView.visibility = View.VISIBLE
-
- // Add each tile.
- tiles.forEachIndexed { i, tile ->
- val tileView =
- PeopleSpaceTileView(root.context, layout, tile.key.shortcutId, i == tiles.size - 1)
- bindTileView(tileView, tile, onTileClicked)
- }
- }
-
- /** Sets [tileView] with the data in [conversation]. */
- private fun bindTileView(
- tileView: PeopleSpaceTileView,
- tile: PeopleTileViewModel,
- onTileClicked: (PeopleTileViewModel) -> Unit,
- ) {
- try {
- tileView.setName(tile.username)
- tileView.setPersonIcon(tile.icon)
- tileView.setOnClickListener { onTileClicked(tile) }
- } catch (e: Exception) {
- Log.e(TAG, "Couldn't retrieve shortcut information", e)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
index 4ee2db7..cc0901f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java
@@ -307,7 +307,7 @@
} else {
// Set the horizontal paddings unless the view is the Compose implementation of the
// footer actions.
- if (view.getTag(R.id.tag_compose_qs_footer_actions) == null) {
+ if (view.getId() != R.id.qs_footer_actions) {
view.setPaddingRelative(
mContentHorizontalPadding,
view.getPaddingTop(),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
index e424975..38d7290 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentLegacy.java
@@ -219,6 +219,13 @@
}
@Override
+ public void setShouldUpdateSquishinessOnMedia(boolean shouldUpdate) {
+ if (mQsImpl != null) {
+ mQsImpl.setShouldUpdateSquishinessOnMedia(shouldUpdate);
+ }
+ }
+
+ @Override
public void setListening(boolean listening) {
if (mQsImpl != null) {
mQsImpl.setListening(listening);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
index 1f4838e..8c0d122 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSImpl.java
@@ -34,11 +34,11 @@
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.LinearLayout;
import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import androidx.compose.ui.platform.ComposeView;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LifecycleRegistry;
@@ -48,15 +48,12 @@
import com.android.systemui.Dumpable;
import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.plugins.qs.QSContainerController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSComponent;
-import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
@@ -117,11 +114,9 @@
private final MediaHost mQqsMediaHost;
private final QSDisableFlagsLogger mQsDisableFlagsLogger;
private final LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
- private final FeatureFlags mFeatureFlags;
private final QSLogger mLogger;
private final FooterActionsController mFooterActionsController;
private final FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
- private final FooterActionsViewBinder mFooterActionsViewBinder;
private final ListeningAndVisibilityLifecycleOwner mListeningAndVisibilityLifecycleOwner;
private boolean mShowCollapsedOnKeyguard;
private boolean mLastKeyguardAndExpanded;
@@ -168,11 +163,14 @@
private boolean mIsSmallScreen;
+ /** Should the squishiness fraction be updated on the media host. */
+ private boolean mShouldUpdateMediaSquishiness;
+
private CommandQueue mCommandQueue;
private View mRootView;
@Nullable
- private View mFooterActionsView;
+ private ComposeView mFooterActionsView;
@Inject
public QSImpl(RemoteInputQuickSettingsDisabler remoteInputQsDisabler,
@@ -184,23 +182,19 @@
DumpManager dumpManager, QSLogger qsLogger,
FooterActionsController footerActionsController,
FooterActionsViewModel.Factory footerActionsViewModelFactory,
- FooterActionsViewBinder footerActionsViewBinder,
- LargeScreenShadeInterpolator largeScreenShadeInterpolator,
- FeatureFlags featureFlags) {
+ LargeScreenShadeInterpolator largeScreenShadeInterpolator) {
mRemoteInputQuickSettingsDisabler = remoteInputQsDisabler;
mQsMediaHost = qsMediaHost;
mQqsMediaHost = qqsMediaHost;
mQsDisableFlagsLogger = qsDisableFlagsLogger;
mLogger = qsLogger;
mLargeScreenShadeInterpolator = largeScreenShadeInterpolator;
- mFeatureFlags = featureFlags;
mCommandQueue = commandQueue;
mBypassController = keyguardBypassController;
mStatusBarStateController = statusBarStateController;
mDumpManager = dumpManager;
mFooterActionsController = footerActionsController;
mFooterActionsViewModelFactory = footerActionsViewModelFactory;
- mFooterActionsViewBinder = footerActionsViewBinder;
mListeningAndVisibilityLifecycleOwner = new ListeningAndVisibilityLifecycleOwner();
if (SceneContainerFlag.isEnabled()) {
mStatusBarState = StatusBarState.SHADE;
@@ -294,43 +288,9 @@
}
private void bindFooterActionsView(View root) {
- LinearLayout footerActionsView = root.findViewById(R.id.qs_footer_actions);
-
- if (!mFeatureFlags.isEnabled(Flags.COMPOSE_QS_FOOTER_ACTIONS)) {
- Log.d(TAG, "Binding the View implementation of the QS footer actions");
- mFooterActionsView = footerActionsView;
- mFooterActionsViewBinder.bind(footerActionsView, mQSFooterActionsViewModel,
- mListeningAndVisibilityLifecycleOwner);
- return;
- }
-
- // Compose is available, so let's use the Compose implementation of the footer actions.
- Log.d(TAG, "Binding the Compose implementation of the QS footer actions");
- View composeView = QSUtils.createFooterActionsView(root.getContext(),
+ mFooterActionsView = root.findViewById(R.id.qs_footer_actions);
+ QSUtils.setFooterActionsViewContent(mFooterActionsView,
mQSFooterActionsViewModel, mListeningAndVisibilityLifecycleOwner);
- mFooterActionsView = composeView;
-
- // The id R.id.qs_footer_actions is used by QSContainerImpl to set the horizontal margin
- // to all views except for qs_footer_actions, so we set it to the Compose view.
- composeView.setId(R.id.qs_footer_actions);
-
- // Set this tag so that QSContainerImpl does not add horizontal paddings to this Compose
- // implementation of the footer actions. They will be set in Compose instead so that the
- // background fills the full screen width.
- composeView.setTag(R.id.tag_compose_qs_footer_actions, true);
-
- // Set the same elevation as the View implementation, otherwise the footer actions will be
- // drawn below the scroll view with QS grid and clicks won't get through on small devices
- // where there isn't enough vertical space to show all the tiles and the footer actions.
- composeView.setElevation(
- composeView.getContext().getResources().getDimension(R.dimen.qs_panel_elevation));
-
- // Replace the View by the Compose provided one.
- ViewGroup parent = (ViewGroup) footerActionsView.getParent();
- ViewGroup.LayoutParams layoutParams = footerActionsView.getLayoutParams();
- int index = parent.indexOfChild(footerActionsView);
- parent.removeViewAt(index);
- parent.addView(composeView, index, layoutParams);
}
@Override
@@ -662,6 +622,12 @@
}
@Override
+ public void setShouldUpdateSquishinessOnMedia(boolean shouldUpdate) {
+ if (DEBUG) Log.d(TAG, "setShouldUpdateSquishinessOnMedia " + shouldUpdate);
+ mShouldUpdateMediaSquishiness = shouldUpdate;
+ }
+
+ @Override
public void setQsExpansion(float expansion, float panelExpansionFraction,
float proposedTranslation, float squishinessFraction) {
float headerTranslation = mTransitioningToFullShade ? 0 : proposedTranslation;
@@ -740,9 +706,11 @@
if (mQSAnimator != null) {
mQSAnimator.setPosition(expansion);
}
- if (!mInSplitShade
+ if (!mShouldUpdateMediaSquishiness
+ && (!mInSplitShade
|| mStatusBarStateController.getState() == KEYGUARD
- || mStatusBarStateController.getState() == SHADE_LOCKED) {
+ || mStatusBarStateController.getState() == SHADE_LOCKED)
+ ) {
// At beginning, state is 0 and will apply wrong squishiness to MediaHost in lockscreen
// and media player expect no change by squishiness in lock screen shade. Don't bother
// squishing mQsMediaHost when not in split shade to prevent problems with stale state.
@@ -1038,6 +1006,7 @@
indentingPw.println("mTransitioningToFullShade: " + mTransitioningToFullShade);
indentingPw.println("mLockscreenToShadeProgress: " + mLockscreenToShadeProgress);
indentingPw.println("mOverScrolling: " + mOverScrolling);
+ indentingPw.println("mShouldUpdateMediaSquishiness: " + mShouldUpdateMediaSquishiness);
indentingPw.println("isCustomizing: " + mQSCustomizerController.isCustomizing());
View view = getView();
if (view != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt
index 15c3f27..5482e6d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt
@@ -1,10 +1,9 @@
package com.android.systemui.qs
import android.content.Context
-import android.view.View
+import androidx.compose.ui.platform.ComposeView
import androidx.lifecycle.LifecycleOwner
import com.android.compose.theme.PlatformTheme
-import com.android.compose.ui.platform.DensityAwareComposeView
import com.android.internal.policy.SystemBarUtils
import com.android.systemui.qs.footer.ui.compose.FooterActions
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
@@ -29,13 +28,11 @@
}
@JvmStatic
- fun createFooterActionsView(
- context: Context,
+ fun setFooterActionsViewContent(
+ view: ComposeView,
viewModel: FooterActionsViewModel,
qsVisibilityLifecycleOwner: LifecycleOwner,
- ): View {
- return DensityAwareComposeView(context).apply {
- setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
- }
+ ) {
+ view.setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
deleted file mode 100644
index 0995dd4..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/binder/FooterActionsViewBinder.kt
+++ /dev/null
@@ -1,329 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs.footer.ui.binder
-
-import android.content.Context
-import android.graphics.PorterDuff
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.ImageView
-import android.widget.LinearLayout
-import android.widget.TextView
-import androidx.core.view.isVisible
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import com.android.systemui.animation.Expandable
-import com.android.systemui.common.ui.binder.IconViewBinder
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.people.ui.view.PeopleViewBinder.bind
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsButtonViewModel
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsForegroundServicesButtonViewModel
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsSecurityButtonViewModel
-import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
-import com.android.systemui.res.R
-import javax.inject.Inject
-import kotlin.math.roundToInt
-import kotlinx.coroutines.launch
-
-/** A ViewBinder for [FooterActionsViewBinder]. */
-@SysUISingleton
-class FooterActionsViewBinder @Inject constructor() {
- /** Create a view that can later be [bound][bind] to a [FooterActionsViewModel]. */
- fun create(context: Context): LinearLayout {
- return LayoutInflater.from(context).inflate(R.layout.footer_actions, /* root= */ null)
- as LinearLayout
- }
-
- /** Bind [view] to [viewModel]. */
- fun bind(
- view: LinearLayout,
- viewModel: FooterActionsViewModel,
- qsVisibilityLifecycleOwner: LifecycleOwner,
- ) {
- view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES
-
- // Add the views used by this new implementation.
- val context = view.context
- val inflater = LayoutInflater.from(context)
-
- val securityHolder = TextButtonViewHolder.createAndAdd(inflater, view)
- val foregroundServicesWithTextHolder = TextButtonViewHolder.createAndAdd(inflater, view)
- val foregroundServicesWithNumberHolder = NumberButtonViewHolder.createAndAdd(inflater, view)
- val userSwitcherHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = false)
- val settingsHolder =
- IconButtonViewHolder.createAndAdd(inflater, view, isLast = viewModel.power == null)
-
- // Bind the static power and settings buttons.
- bindButton(settingsHolder, viewModel.settings)
-
- if (viewModel.power != null) {
- val powerHolder = IconButtonViewHolder.createAndAdd(inflater, view, isLast = true)
- bindButton(powerHolder, viewModel.power)
- }
-
- // There are 2 lifecycle scopes we are using here:
- // 1) The scope created by [repeatWhenAttached] when [view] is attached, and destroyed
- // when the [view] is detached. We use this as the parent scope for all our [viewModel]
- // state collection, given that we don't want to do any work when [view] is detached.
- // 2) The scope owned by [lifecycleOwner], which should be RESUMED only when Quick
- // Settings are visible. We use this to make sure we collect UI state only when the
- // View is visible.
- //
- // Given that we start our collection when the Quick Settings become visible, which happens
- // every time the user swipes down the shade, we remember our previous UI state already
- // bound to the UI to avoid binding the same values over and over for nothing.
-
- // TODO(b/242040009): Look into using only a single scope.
-
- var previousSecurity: FooterActionsSecurityButtonViewModel? = null
- var previousForegroundServices: FooterActionsForegroundServicesButtonViewModel? = null
- var previousUserSwitcher: FooterActionsButtonViewModel? = null
-
- // Listen for ViewModel updates when the View is attached.
- view.repeatWhenAttached {
- val attachedScope = this.lifecycleScope
-
- attachedScope.launch {
- // Listen for dialog requests as soon as we are attached, even when not visible.
- // TODO(b/242040009): Should this move somewhere else?
- launch { viewModel.observeDeviceMonitoringDialogRequests(view.context) }
-
- // Make sure we set the correct alphas even when QS are not currently shown.
- launch { viewModel.alpha.collect { view.alpha = it } }
- launch {
- viewModel.backgroundAlpha.collect {
- view.background?.alpha = (it * 255).roundToInt()
- }
- }
- }
-
- // Listen for model changes only when QS are visible.
- qsVisibilityLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
- // Security.
- launch {
- viewModel.security.collect { security ->
- if (previousSecurity != security) {
- bindSecurity(view.context, securityHolder, security)
- previousSecurity = security
- }
- }
- }
-
- // Foreground services.
- launch {
- viewModel.foregroundServices.collect { foregroundServices ->
- if (previousForegroundServices != foregroundServices) {
- bindForegroundService(
- foregroundServicesWithNumberHolder,
- foregroundServicesWithTextHolder,
- foregroundServices,
- )
- previousForegroundServices = foregroundServices
- }
- }
- }
-
- // User switcher.
- launch {
- viewModel.userSwitcher.collect { userSwitcher ->
- if (previousUserSwitcher != userSwitcher) {
- bindButton(userSwitcherHolder, userSwitcher)
- previousUserSwitcher = userSwitcher
- }
- }
- }
- }
- }
- }
-
- private fun bindSecurity(
- quickSettingsContext: Context,
- securityHolder: TextButtonViewHolder,
- security: FooterActionsSecurityButtonViewModel?,
- ) {
- val securityView = securityHolder.view
- securityView.isVisible = security != null
- if (security == null) {
- return
- }
-
- // Make sure that the chevron is visible and that the button is clickable if there is a
- // listener.
- val chevron = securityHolder.chevron
- val onClick = security.onClick
- if (onClick != null) {
- securityView.isClickable = true
- securityView.setOnClickListener {
- onClick(quickSettingsContext, Expandable.fromView(securityView))
- }
- chevron.isVisible = true
- } else {
- securityView.isClickable = false
- securityView.setOnClickListener(null)
- chevron.isVisible = false
- }
-
- securityHolder.text.text = security.text
- securityHolder.newDot.isVisible = false
- IconViewBinder.bind(security.icon, securityHolder.icon)
- }
-
- private fun bindForegroundService(
- foregroundServicesWithNumberHolder: NumberButtonViewHolder,
- foregroundServicesWithTextHolder: TextButtonViewHolder,
- foregroundServices: FooterActionsForegroundServicesButtonViewModel?,
- ) {
- val foregroundServicesWithNumberView = foregroundServicesWithNumberHolder.view
- val foregroundServicesWithTextView = foregroundServicesWithTextHolder.view
- if (foregroundServices == null) {
- foregroundServicesWithNumberView.isVisible = false
- foregroundServicesWithTextView.isVisible = false
- return
- }
-
- val foregroundServicesCount = foregroundServices.foregroundServicesCount
- if (foregroundServices.displayText) {
- // Button with text, icon and chevron.
- foregroundServicesWithNumberView.isVisible = false
-
- foregroundServicesWithTextView.isVisible = true
- foregroundServicesWithTextView.setOnClickListener {
- foregroundServices.onClick(Expandable.fromView(foregroundServicesWithTextView))
- }
- foregroundServicesWithTextHolder.text.text = foregroundServices.text
- foregroundServicesWithTextHolder.newDot.isVisible = foregroundServices.hasNewChanges
- } else {
- // Small button with the number only.
- foregroundServicesWithTextView.isVisible = false
-
- foregroundServicesWithNumberView.isVisible = true
- foregroundServicesWithNumberView.setOnClickListener {
- foregroundServices.onClick(Expandable.fromView(foregroundServicesWithNumberView))
- }
- foregroundServicesWithNumberHolder.number.text = foregroundServicesCount.toString()
- foregroundServicesWithNumberHolder.number.contentDescription = foregroundServices.text
- foregroundServicesWithNumberHolder.newDot.isVisible = foregroundServices.hasNewChanges
- }
- }
-
- private fun bindButton(button: IconButtonViewHolder, model: FooterActionsButtonViewModel?) {
- val buttonView = button.view
- buttonView.id = model?.id ?: View.NO_ID
- buttonView.isVisible = model != null
- if (model == null) {
- return
- }
-
- val backgroundResource =
- when (model.backgroundColor) {
- R.attr.shadeInactive -> R.drawable.qs_footer_action_circle
- R.attr.shadeActive -> R.drawable.qs_footer_action_circle_color
- else -> error("Unsupported icon background resource ${model.backgroundColor}")
- }
- buttonView.setBackgroundResource(backgroundResource)
- buttonView.setOnClickListener { model.onClick(Expandable.fromView(buttonView)) }
-
- val icon = model.icon
- val iconView = button.icon
-
- IconViewBinder.bind(icon, iconView)
- if (model.iconTint != null) {
- iconView.setColorFilter(model.iconTint, PorterDuff.Mode.SRC_IN)
- } else {
- iconView.clearColorFilter()
- }
- }
-}
-
-private class TextButtonViewHolder(val view: View) {
- val icon = view.requireViewById<ImageView>(R.id.icon)
- val text = view.requireViewById<TextView>(R.id.text)
- val newDot = view.requireViewById<ImageView>(R.id.new_dot)
- val chevron = view.requireViewById<ImageView>(R.id.chevron_icon)
-
- companion object {
- fun createAndAdd(inflater: LayoutInflater, root: ViewGroup): TextButtonViewHolder {
- val view =
- inflater.inflate(
- R.layout.footer_actions_text_button,
- /* root= */ root,
- /* attachToRoot= */ false,
- )
- root.addView(view)
- return TextButtonViewHolder(view)
- }
- }
-}
-
-private class NumberButtonViewHolder(val view: View) {
- val number = view.requireViewById<TextView>(R.id.number)
- val newDot = view.requireViewById<ImageView>(R.id.new_dot)
-
- companion object {
- fun createAndAdd(inflater: LayoutInflater, root: ViewGroup): NumberButtonViewHolder {
- val view =
- inflater.inflate(
- R.layout.footer_actions_number_button,
- /* root= */ root,
- /* attachToRoot= */ false,
- )
- root.addView(view)
- return NumberButtonViewHolder(view)
- }
- }
-}
-
-private class IconButtonViewHolder(val view: View) {
- val icon = view.requireViewById<ImageView>(R.id.icon)
-
- companion object {
- fun createAndAdd(
- inflater: LayoutInflater,
- root: ViewGroup,
- isLast: Boolean,
- ): IconButtonViewHolder {
- val view =
- inflater.inflate(
- R.layout.footer_actions_icon_button,
- /* root= */ root,
- /* attachToRoot= */ false,
- )
-
- // All buttons have a background with an inset of qs_footer_action_inset, so the last
- // button must have a negative inset of -qs_footer_action_inset to compensate and be
- // aligned with its parent.
- val marginEnd =
- if (isLast) {
- -view.context.resources.getDimensionPixelSize(R.dimen.qs_footer_action_inset)
- } else {
- 0
- }
-
- val size =
- view.context.resources.getDimensionPixelSize(R.dimen.qs_footer_action_button_size)
- root.addView(
- view,
- LinearLayout.LayoutParams(size, size).apply { this.marginEnd = marginEnd },
- )
- return IconButtonViewHolder(view)
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
index e581bfc..095bdf2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/IconTilesRepository.kt
@@ -19,38 +19,26 @@
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-/** Repository for retrieving the list of [TileSpec] to be displayed as icons. */
+/** Repository for checking if a tile should be displayed as an icon. */
interface IconTilesRepository {
- val iconTilesSpecs: StateFlow<Set<TileSpec>>
+ fun isIconTile(spec: TileSpec): Boolean
}
@SysUISingleton
class IconTilesRepositoryImpl @Inject constructor() : IconTilesRepository {
- private val _iconTilesSpecs =
- MutableStateFlow(
- setOf(
- TileSpec.create("airplane"),
- TileSpec.create("battery"),
- TileSpec.create("cameratoggle"),
- TileSpec.create("cast"),
- TileSpec.create("color_correction"),
- TileSpec.create("inversion"),
- TileSpec.create("saver"),
- TileSpec.create("dnd"),
- TileSpec.create("flashlight"),
- TileSpec.create("location"),
- TileSpec.create("mictoggle"),
- TileSpec.create("nfc"),
- TileSpec.create("night"),
- TileSpec.create("rotation")
- )
- )
+ override fun isIconTile(spec: TileSpec): Boolean {
+ return !LARGE_TILES.contains(spec)
+ }
- /** Set of toggleable tiles that are suitable for being shown as an icon. */
- override val iconTilesSpecs: StateFlow<Set<TileSpec>> = _iconTilesSpecs.asStateFlow()
+ companion object {
+ private val LARGE_TILES =
+ setOf(
+ TileSpec.create("internet"),
+ TileSpec.create("bt"),
+ TileSpec.create("dnd"),
+ TileSpec.create("cast"),
+ )
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
index ccc1c6e..524ea8b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/IconTilesInteractor.kt
@@ -20,10 +20,9 @@
import com.android.systemui.qs.panels.data.repository.IconTilesRepository
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
/** Interactor for retrieving the list of [TileSpec] to be displayed as icons. */
@SysUISingleton
-class IconTilesInteractor @Inject constructor(repo: IconTilesRepository) {
- val iconTilesSpecs: StateFlow<Set<TileSpec>> = repo.iconTilesSpecs
+class IconTilesInteractor @Inject constructor(private val repo: IconTilesRepository) {
+ fun isIconTile(spec: TileSpec): Boolean = repo.isIconTile(spec)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
index b437f64..e99c64c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractor.kt
@@ -38,14 +38,13 @@
override fun reconcileTiles(tiles: List<TileSpec>): List<TileSpec> {
val newTiles: MutableList<TileSpec> = mutableListOf()
val row = TileRow<TileSpec>(columns = gridSizeInteractor.columns.value)
- val iconTilesSet = iconTilesInteractor.iconTilesSpecs.value
val tilesQueue =
ArrayDeque(
tiles.map {
SizedTile(
it,
width =
- if (iconTilesSet.contains(it)) {
+ if (iconTilesInteractor.isIconTile(it)) {
1
} else {
2
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index 4aeaa7d..2f0fe22 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -52,15 +52,13 @@
tiles.forEach { it.startListening(token) }
onDispose { tiles.forEach { it.stopListening(token) } }
}
- val iconTilesSpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
items(
tiles.size,
span = { index ->
- val iconOnly = iconTilesSpecs.contains(tiles[index].spec)
- if (iconOnly) {
+ if (iconTilesViewModel.isIconTile(tiles[index].spec)) {
GridItemSpan(1)
} else {
GridItemSpan(2)
@@ -69,7 +67,7 @@
) { index ->
Tile(
tile = tiles[index],
- iconOnly = iconTilesSpecs.contains(tiles[index].spec),
+ iconOnly = iconTilesViewModel.isIconTile(tiles[index].spec),
modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
)
}
@@ -83,12 +81,11 @@
onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit,
) {
- val iconOnlySpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
DefaultEditTileGrid(
tiles = tiles,
- iconOnlySpecs = iconOnlySpecs,
+ isIconOnly = iconTilesViewModel::isIconTile,
columns = GridCells.Fixed(columns),
modifier = modifier,
onAddTile = onAddTile,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
index 708ef0d..d600767 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PartitionedGridLayout.kt
@@ -38,7 +38,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
-import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.dimensionResource
@@ -66,12 +65,11 @@
tiles.forEach { it.startListening(token) }
onDispose { tiles.forEach { it.stopListening(token) } }
}
- val iconTilesSpecs by viewModel.iconTilesSpecs.collectAsStateWithLifecycle()
val columns by viewModel.columns.collectAsStateWithLifecycle()
val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
val largeTileHeight = tileHeight()
val iconTileHeight = tileHeight(showLabels)
- val (smallTiles, largeTiles) = tiles.partition { iconTilesSpecs.contains(it.spec) }
+ val (smallTiles, largeTiles) = tiles.partition { viewModel.isIconTile(it.spec) }
TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
// Large tiles
@@ -103,7 +101,6 @@
onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit
) {
- val iconOnlySpecs by viewModel.iconTilesSpecs.collectAsStateWithLifecycle()
val columns by viewModel.columns.collectAsStateWithLifecycle()
val showLabels by viewModel.showLabels.collectAsStateWithLifecycle()
@@ -111,8 +108,6 @@
val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
}
- val isIconOnly: (TileSpec) -> Boolean =
- remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
val largeTileHeight = tileHeight()
val iconTileHeight = tileHeight(showLabels)
val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
@@ -151,7 +146,7 @@
iconTileHeight = iconTileHeight,
tilePadding = tilePadding,
onRemoveTile = onRemoveTile,
- isIconOnly = isIconOnly,
+ isIconOnly = viewModel::isIconTile,
columns = columns,
showLabels = showLabels,
)
@@ -161,7 +156,7 @@
iconTileHeight = iconTileHeight,
tilePadding = tilePadding,
addTileToEnd = addTileToEnd,
- isIconOnly = isIconOnly,
+ isIconOnly = viewModel::isIconTile,
showLabels = showLabels,
columns = columns,
)
@@ -232,7 +227,7 @@
val largeGridHeight = gridHeight(largeTiles.size, largeTileHeight, columns / 2, tilePadding)
val smallGridHeight = gridHeight(smallTiles.size, iconTileHeight, columns, tilePadding)
val largeGridHeightCustom =
- gridHeight(tilesCustom.size, largeTileHeight, columns / 2, tilePadding)
+ gridHeight(tilesCustom.size, iconTileHeight, columns, tilePadding)
// Add up the height of all three grids and add padding in between
val gridHeight =
@@ -257,8 +252,14 @@
)
fillUpRow(nTiles = smallTiles.size, columns = columns)
- // Custom tiles, all large
- editTiles(tilesCustom, ClickAction.ADD, addTileToEnd, isIconOnly)
+ // Custom tiles, all icons
+ editTiles(
+ tilesCustom,
+ ClickAction.ADD,
+ addTileToEnd,
+ isIconOnly,
+ showLabels = showLabels
+ )
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
index 70d629f..7f4e0a7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/StretchedGridLayout.kt
@@ -60,14 +60,13 @@
// Icon [3 | 4]
// Large [6 | 8]
val columns = 12
- val iconTilesSpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
val stretchedTiles =
remember(tiles) {
val sizedTiles =
tiles.map {
SizedTile(
it,
- if (iconTilesSpecs.contains(it.spec)) {
+ if (iconTilesViewModel.isIconTile(it.spec)) {
3
} else {
6
@@ -81,7 +80,7 @@
items(stretchedTiles.size, span = { GridItemSpan(stretchedTiles[it].width) }) { index ->
Tile(
tile = stretchedTiles[index].tile,
- iconOnly = iconTilesSpecs.contains(stretchedTiles[index].tile.spec),
+ iconOnly = iconTilesViewModel.isIconTile(stretchedTiles[index].tile.spec),
modifier = Modifier.height(dimensionResource(id = R.dimen.qs_tile_height))
)
}
@@ -95,12 +94,11 @@
onAddTile: (TileSpec, Int) -> Unit,
onRemoveTile: (TileSpec) -> Unit
) {
- val iconOnlySpecs by iconTilesViewModel.iconTilesSpecs.collectAsStateWithLifecycle()
val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
DefaultEditTileGrid(
tiles = tiles,
- iconOnlySpecs = iconOnlySpecs,
+ isIconOnly = iconTilesViewModel::isIconTile,
columns = GridCells.Fixed(columns),
modifier = modifier,
onAddTile = onAddTile,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index a6838c0..f776bf0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -165,7 +165,7 @@
@Composable
fun DefaultEditTileGrid(
tiles: List<EditTileViewModel>,
- iconOnlySpecs: Set<TileSpec>,
+ isIconOnly: (TileSpec) -> Boolean,
columns: GridCells,
modifier: Modifier,
onAddTile: (TileSpec, Int) -> Unit,
@@ -176,8 +176,6 @@
val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
}
- val isIconOnly: (TileSpec) -> Boolean =
- remember(iconOnlySpecs) { { tileSpec: TileSpec -> tileSpec in iconOnlySpecs } }
TileLazyGrid(modifier = modifier, columns = columns) {
// These Text are just placeholders to see the different sections. Not final UI.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
index 9ad00c8..117c85c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/IconTilesViewModel.kt
@@ -20,14 +20,13 @@
import com.android.systemui.qs.panels.domain.interactor.IconTilesInteractor
import com.android.systemui.qs.pipeline.shared.TileSpec
import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
interface IconTilesViewModel {
- val iconTilesSpecs: StateFlow<Set<TileSpec>>
+ fun isIconTile(spec: TileSpec): Boolean
}
@SysUISingleton
-class IconTilesViewModelImpl @Inject constructor(interactor: IconTilesInteractor) :
+class IconTilesViewModelImpl @Inject constructor(private val interactor: IconTilesInteractor) :
IconTilesViewModel {
- override val iconTilesSpecs = interactor.iconTilesSpecs
+ override fun isIconTile(spec: TileSpec): Boolean = interactor.isIconTile(spec)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
index 214e9f0..24b80b8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/TileSpecRepository.kt
@@ -158,6 +158,9 @@
override suspend fun prependDefault(
userId: Int,
) {
+ if (retailModeRepository.inRetailMode) {
+ return
+ }
userTileRepositories.get(userId)?.prependDefault()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
index 8ad5cb2..aca8733 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserTileSpecRepository.kt
@@ -60,15 +60,21 @@
_tiles =
changeEvents
.scan(loadTilesFromSettingsAndParse(userId)) { current, change ->
- change.apply(current).also {
- if (current != it) {
- if (change is RestoreTiles) {
- logger.logTilesRestoredAndReconciled(current, it, userId)
- } else {
- logger.logProcessTileChange(change, it, userId)
+ change
+ .apply(current)
+ .also {
+ if (current != it) {
+ if (change is RestoreTiles) {
+ logger.logTilesRestoredAndReconciled(current, it, userId)
+ } else {
+ logger.logProcessTileChange(change, it, userId)
+ }
}
}
- }
+ // Distinct preserves the order of the elements removing later
+ // duplicates,
+ // all tiles should be different
+ .distinct()
}
.flowOn(backgroundDispatcher)
.stateIn(applicationScope)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index b7fcef4..97b5e87 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -43,6 +43,7 @@
import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
import com.android.systemui.qs.tiles.di.NewQSTileFactory
import com.android.systemui.qs.toProto
+import com.android.systemui.retail.data.repository.RetailModeRepository
import com.android.systemui.settings.UserTracker
import com.android.systemui.user.data.repository.UserRepository
import com.android.systemui.util.kotlin.pairwise
@@ -137,6 +138,7 @@
private val installedTilesComponentRepository: InstalledTilesComponentRepository,
private val userRepository: UserRepository,
private val minimumTilesRepository: MinimumTilesRepository,
+ private val retailModeRepository: RetailModeRepository,
private val customTileStatePersister: CustomTileStatePersister,
private val newQSTileFactory: Lazy<NewQSTileFactory>,
private val tileFactory: QSFactory,
@@ -178,6 +180,14 @@
installedTilesComponentRepository.getInstalledTilesComponents(it)
}
+ private val minTiles: Int
+ get() =
+ if (retailModeRepository.inRetailMode) {
+ 1
+ } else {
+ minimumTilesRepository.minNumberOfTiles
+ }
+
init {
if (featureFlags.pipelineEnabled) {
startTileCollection()
@@ -273,7 +283,7 @@
newTileMap.filter { it.value is TileOrNotInstalled.NotInstalled }.keys,
newUser
)
- if (newResolvedTiles.size < minimumTilesRepository.minNumberOfTiles) {
+ if (newResolvedTiles.size < minTiles) {
// We ended up with not enough tiles (some may be not installed).
// Prepend the default set of tiles
launch { tileSpecRepository.prependDefault(currentUser.value) }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index c6dfdd5..762dacd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -19,6 +19,7 @@
import android.animation.ArgbEvaluator
import android.animation.PropertyValuesHolder
import android.animation.ValueAnimator
+import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.content.res.Configuration
@@ -37,6 +38,7 @@
import android.util.TypedValue
import android.view.Gravity
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityEvent
@@ -332,6 +334,21 @@
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
updateHeight()
+ maybeUpdateLongPressEffectDimensions()
+ }
+
+ private fun maybeUpdateLongPressEffectDimensions() {
+ if (!isLongClickable || longPressEffect == null) return
+
+ val actualHeight = if (heightOverride != HeightOverrideable.NO_OVERRIDE) {
+ heightOverride
+ } else {
+ measuredHeight
+ }
+ initialLongPressProperties?.height = actualHeight.toFloat()
+ initialLongPressProperties?.width = measuredWidth.toFloat()
+ finalLongPressProperties?.height = LONG_PRESS_EFFECT_HEIGHT_SCALE * actualHeight
+ finalLongPressProperties?.width = LONG_PRESS_EFFECT_WIDTH_SCALE * measuredWidth
}
override fun onFocusChanged(gainFocus: Boolean, direction: Int, previouslyFocusedRect: Rect?) {
@@ -380,15 +397,22 @@
override fun init(tile: QSTile) {
val expandable = Expandable.fromView(this)
- init(
+ if (quickSettingsVisualHapticsLongpress()) {
+ isHapticFeedbackEnabled = false
+ longPressEffect?.qsTile = tile
+ longPressEffect?.expandable = expandable
+ init(
+ { _: View? -> longPressEffect?.onTileClick() },
+ null, // Haptics and long-clicks will be handled by the [QSLongPressEffect]
+ )
+ } else {
+ init(
{ _: View? -> tile.click(expandable) },
{ _: View? ->
tile.longClick(expandable)
true
- }
- )
- if (quickSettingsVisualHapticsLongpress()) {
- isHapticFeedbackEnabled = false // Haptics will be handled by the [QSLongPressEffect]
+ },
+ )
}
}
@@ -526,6 +550,20 @@
return sb.toString()
}
+ @SuppressLint("ClickableViewAccessibility")
+ override fun onTouchEvent(event: MotionEvent?): Boolean {
+ // let the View run the onTouch logic for click and long-click detection
+ val result = super.onTouchEvent(event)
+ if (longPressEffect != null) {
+ when (event?.actionMasked) {
+ MotionEvent.ACTION_DOWN -> longPressEffect.handleActionDown()
+ MotionEvent.ACTION_UP -> longPressEffect.handleActionUp()
+ MotionEvent.ACTION_CANCEL -> longPressEffect.handleActionCancel()
+ }
+ }
+ return result
+ }
+
// HANDLE STATE CHANGES RELATED METHODS
protected open fun handleStateChanged(state: QSTile.State) {
@@ -660,7 +698,6 @@
// Long-press effects might have been enabled before but the new state does not
// handle a long-press. In this case, we go back to the behaviour of a regular tile
// and clean-up the resources
- setOnTouchListener(null)
unbindLongPressEffect()
showRippleEffect = isClickable
initialLongPressProperties = null
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index 1d8b7e5b..bf0843b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -20,10 +20,12 @@
import android.graphics.Rect
import android.os.PowerManager
import android.os.SystemClock
+import android.util.ArraySet
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
+import android.widget.FrameLayout
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
@@ -35,6 +37,7 @@
import androidx.lifecycle.repeatOnLifecycle
import com.android.compose.theme.PlatformTheme
import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.Flags.glanceableHubFullscreenSwipe
import com.android.systemui.ambient.touch.TouchMonitor
import com.android.systemui.ambient.touch.dagger.AmbientTouchComponent
import com.android.systemui.communal.dagger.Communal
@@ -52,10 +55,12 @@
import com.android.systemui.scene.shared.flag.SceneContainerFlag
import com.android.systemui.scene.shared.model.SceneDataSourceDelegator
import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf
import com.android.systemui.util.kotlin.BooleanFlowOperators.not
import com.android.systemui.util.kotlin.collectFlow
+import java.util.function.Consumer
import javax.inject.Inject
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.launch
@@ -77,11 +82,38 @@
private val communalColors: CommunalColors,
private val ambientTouchComponentFactory: AmbientTouchComponent.Factory,
private val communalContent: CommunalContent,
- @Communal private val dataSourceDelegator: SceneDataSourceDelegator
+ @Communal private val dataSourceDelegator: SceneDataSourceDelegator,
+ private val notificationStackScrollLayoutController: NotificationStackScrollLayoutController,
) : LifecycleOwner {
+
+ private class CommunalWrapper(context: Context) : FrameLayout(context) {
+ private val consumers: MutableSet<Consumer<Boolean>> = ArraySet()
+
+ override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
+ consumers.forEach { it.accept(disallowIntercept) }
+ super.requestDisallowInterceptTouchEvent(disallowIntercept)
+ }
+
+ fun dispatchTouchEvent(
+ ev: MotionEvent?,
+ disallowInterceptConsumer: Consumer<Boolean>?
+ ): Boolean {
+ disallowInterceptConsumer?.apply { consumers.add(this) }
+
+ try {
+ return super.dispatchTouchEvent(ev)
+ } finally {
+ consumers.clear()
+ }
+ }
+ }
+
/** The container view for the hub. This will not be initialized until [initView] is called. */
private var communalContainerView: View? = null
+ /** Wrapper around the communal container to intercept touch events */
+ private var communalContainerWrapper: CommunalWrapper? = null
+
/**
* This lifecycle is used to control when the [touchMonitor] listens to touches. The lifecycle
* should only be [Lifecycle.State.RESUMED] when the hub is showing and not covered by anything,
@@ -271,9 +303,13 @@
)
collectFlow(containerView, keyguardInteractor.isDreaming, { isDreaming = it })
- communalContainerView = containerView
-
- return containerView
+ if (glanceableHubFullscreenSwipe()) {
+ communalContainerWrapper = CommunalWrapper(containerView.context)
+ communalContainerWrapper?.addView(communalContainerView)
+ return communalContainerWrapper!!
+ } else {
+ return containerView
+ }
}
/**
@@ -306,6 +342,11 @@
lifecycleRegistry.currentState = Lifecycle.State.CREATED
communalContainerView = null
}
+
+ communalContainerWrapper?.let {
+ (it.parent as ViewGroup).removeView(it)
+ communalContainerWrapper = null
+ }
}
/**
@@ -319,6 +360,18 @@
*/
fun onTouchEvent(ev: MotionEvent): Boolean {
SceneContainerFlag.assertInLegacyMode()
+
+ // In the case that we are handling full swipes on the lockscreen, are on the lockscreen,
+ // and the touch is within the horizontal notification band on the screen, do not process
+ // the touch.
+ if (
+ glanceableHubFullscreenSwipe() &&
+ !hubShowing &&
+ !notificationStackScrollLayoutController.isBelowLastNotification(ev.x, ev.y)
+ ) {
+ return false
+ }
+
return communalContainerView?.let { handleTouchEventOnCommunalView(it, ev) } ?: false
}
@@ -330,12 +383,16 @@
val hubOccluded = anyBouncerShowing || shadeShowing
if (isDown && !hubOccluded) {
- val x = ev.rawX
- val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth
- if (inOpeningSwipeRegion || hubShowing) {
- // Steal touch events when the hub is open, or if the touch started in the opening
- // gesture region.
+ if (glanceableHubFullscreenSwipe()) {
isTrackingHubTouch = true
+ } else {
+ val x = ev.rawX
+ val inOpeningSwipeRegion: Boolean = x >= view.width - rightEdgeSwipeRegionWidth
+ if (inOpeningSwipeRegion || hubShowing) {
+ // Steal touch events when the hub is open, or if the touch started in the
+ // opening gesture region.
+ isTrackingHubTouch = true
+ }
}
}
@@ -343,10 +400,7 @@
if (isUp || isCancel) {
isTrackingHubTouch = false
}
- dispatchTouchEvent(view, ev)
- // Return true regardless of dispatch result as some touches at the start of a gesture
- // may return false from dispatchTouchEvent.
- return true
+ return dispatchTouchEvent(view, ev)
}
return false
@@ -356,13 +410,30 @@
* Dispatches the touch event to the communal container and sends a user activity event to reset
* the screen timeout.
*/
- private fun dispatchTouchEvent(view: View, ev: MotionEvent) {
- view.dispatchTouchEvent(ev)
- powerManager.userActivity(
- SystemClock.uptimeMillis(),
- PowerManager.USER_ACTIVITY_EVENT_TOUCH,
- 0
- )
+ private fun dispatchTouchEvent(view: View, ev: MotionEvent): Boolean {
+ try {
+ var handled = false
+ if (glanceableHubFullscreenSwipe()) {
+ communalContainerWrapper?.dispatchTouchEvent(ev) {
+ if (it) {
+ handled = true
+ }
+ }
+ return handled || hubShowing
+ } else {
+ view.dispatchTouchEvent(ev)
+ // Return true regardless of dispatch result as some touches at the start of a
+ // gesture
+ // may return false from dispatchTouchEvent.
+ return true
+ }
+ } finally {
+ powerManager.userActivity(
+ SystemClock.uptimeMillis(),
+ PowerManager.USER_ACTIVITY_EVENT_TOUCH,
+ 0
+ )
+ }
}
override val lifecycle: Lifecycle
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
index 6df8ac4..4f6a64f 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsControllerImpl.java
@@ -65,6 +65,7 @@
import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.classifier.Classifier;
+import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor;
import com.android.systemui.dump.DumpManager;
@@ -157,6 +158,7 @@
private final ShadeRepository mShadeRepository;
private final ShadeInteractor mShadeInteractor;
private final ActiveNotificationsInteractor mActiveNotificationsInteractor;
+ private final Lazy<CommunalTransitionViewModel> mCommunalTransitionViewModelLazy;
private final JavaAdapter mJavaAdapter;
private final FalsingManager mFalsingManager;
private final AccessibilityManager mAccessibilityManager;
@@ -334,6 +336,7 @@
JavaAdapter javaAdapter,
CastController castController,
SplitShadeStateController splitShadeStateController,
+ Lazy<CommunalTransitionViewModel> communalTransitionViewModelLazy,
Lazy<LargeScreenHeaderHelper> largeScreenHeaderHelperLazy
) {
SceneContainerFlag.assertInLegacyMode();
@@ -379,6 +382,7 @@
mShadeRepository = shadeRepository;
mShadeInteractor = shadeInteractor;
mActiveNotificationsInteractor = activeNotificationsInteractor;
+ mCommunalTransitionViewModelLazy = communalTransitionViewModelLazy;
mJavaAdapter = javaAdapter;
mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback());
@@ -458,6 +462,9 @@
initNotificationStackScrollLayoutController();
mJavaAdapter.alwaysCollectFlow(
mShadeInteractor.isExpandToQsEnabled(), this::setExpansionEnabledPolicy);
+ mJavaAdapter.alwaysCollectFlow(
+ mCommunalTransitionViewModelLazy.get().isUmoOnCommunal(),
+ this::setShouldUpdateSquishinessOnMedia);
}
private void initNotificationStackScrollLayoutController() {
@@ -892,6 +899,12 @@
}
}
+ private void setShouldUpdateSquishinessOnMedia(boolean shouldUpdate) {
+ if (mQs != null) {
+ mQs.setShouldUpdateSquishinessOnMedia(shouldUpdate);
+ }
+ }
+
void setOverScrollAmount(int overExpansion) {
if (mQs != null) {
mQs.setOverScrollAmount(overExpansion);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
index 5e4b173..a171d33 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupController.java
@@ -381,7 +381,10 @@
mLogger.logHandleUpdateCarrierInfo(info);
mNoSimTextView.setVisibility(View.GONE);
- if (!info.airplaneMode && info.anySimReady) {
+ if (info.isInSatelliteMode) {
+ mLogger.logUsingSatelliteText(info.carrierText);
+ showSingleText(info.carrierText);
+ } else if (!info.airplaneMode && info.anySimReady) {
boolean[] slotSeen = new boolean[SIM_SLOTS];
if (info.listOfCarriers.length == info.subscriptionIds.length) {
mLogger.logUsingSimViews();
@@ -416,22 +419,31 @@
info.listOfCarriers.length, info.subscriptionIds.length);
}
} else {
+ // No sims or airplane mode (but not WFC), so just show the main carrier text.
mLogger.logUsingNoSimView(info.carrierText);
- // No sims or airplane mode (but not WFC). Do not show ShadeCarrierGroup,
- // instead just show info.carrierText in a different view.
- for (int i = 0; i < SIM_SLOTS; i++) {
- mInfos[i] = mInfos[i].changeVisibility(false);
- mCarrierGroups[i].setCarrierText("");
- mCarrierGroups[i].setVisibility(View.GONE);
- }
- mNoSimTextView.setText(info.carrierText);
- if (!TextUtils.isEmpty(info.carrierText)) {
- mNoSimTextView.setVisibility(View.VISIBLE);
- }
+ showSingleText(info.carrierText);
}
handleUpdateState(); // handleUpdateCarrierInfo is always called from main thread.
}
+ /**
+ * Shows only the given text in a single TextView and hides ShadeCarrierGroup (which would show
+ * individual SIM details).
+ */
+ private void showSingleText(CharSequence text) {
+ for (int i = 0; i < SIM_SLOTS; i++) {
+ mInfos[i] = mInfos[i].changeVisibility(false);
+ mCarrierGroups[i].setCarrierText("");
+ mCarrierGroups[i].setVisibility(View.GONE);
+ }
+ // TODO(b/341841138): Re-name this view now that it's being used for more than just the
+ // no-SIM case.
+ mNoSimTextView.setText(text);
+ if (!TextUtils.isEmpty(text)) {
+ mNoSimTextView.setVisibility(View.VISIBLE);
+ }
+ }
+
private static class H extends Handler {
private Consumer<CarrierTextManager.CarrierTextCallbackInfo> mUpdateCarrierInfo;
private Runnable mUpdateState;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt
index af06a35..b563cd9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerLogger.kt
@@ -65,6 +65,15 @@
)
}
+ fun logUsingSatelliteText(text: CharSequence) {
+ buffer.log(
+ TAG,
+ LogLevel.VERBOSE,
+ { str1 = "$text" },
+ { "â”— updating No SIM view with satellite text=$str1" },
+ )
+ }
+
fun logUsingSimViews() {
buffer.log(TAG, LogLevel.VERBOSE, {}, { "â”— updating SIM views" })
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
index 9885fe4..e7fc18e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorImpl.kt
@@ -17,6 +17,7 @@
package com.android.systemui.shade.domain.interactor
import com.android.keyguard.LockIconViewController
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.scene.domain.interactor.SceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -30,7 +31,8 @@
class ShadeLockscreenInteractorImpl
@Inject
constructor(
- @Background private val scope: CoroutineScope,
+ @Application private val applicationScope: CoroutineScope,
+ @Background private val backgroundScope: CoroutineScope,
private val shadeInteractor: ShadeInteractor,
private val sceneInteractor: SceneInteractor,
private val lockIconViewController: LockIconViewController,
@@ -68,7 +70,7 @@
// Now handled elsewhere. Do nothing.
}
override fun transitionToExpandedShade(delay: Long) {
- scope.launch {
+ backgroundScope.launch {
delay(delay)
changeToShadeScene()
}
@@ -96,10 +98,12 @@
}
private fun changeToShadeScene() {
- val shadeMode = shadeInteractor.shadeMode.value
- sceneInteractor.changeScene(
- if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade,
- "ShadeLockscreenInteractorImpl.expandToNotifications",
- )
+ applicationScope.launch {
+ val shadeMode = shadeInteractor.shadeMode.value
+ sceneInteractor.changeScene(
+ if (shadeMode is ShadeMode.Dual) Scenes.NotificationsShade else Scenes.Shade,
+ "ShadeLockscreenInteractorImpl.expandToNotifications",
+ )
+ }
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index d00916a..c742f641 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -610,7 +610,7 @@
keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
mContext.getString(R.string.keyboard_shortcut_group_applications_calendar),
calendarIcon,
- KeyEvent.KEYCODE_L,
+ KeyEvent.KEYCODE_K,
KeyEvent.META_META_ON));
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
index 3cf61e2..8d3f728 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationGroupingUtil.java
@@ -362,20 +362,34 @@
}
protected boolean hasSameIcon(Object parentData, Object childData) {
- Icon parentIcon = ((Notification) parentData).getSmallIcon();
- Icon childIcon = ((Notification) childData).getSmallIcon();
+ Icon parentIcon = getIcon((Notification) parentData);
+ Icon childIcon = getIcon((Notification) childData);
return parentIcon.sameAs(childIcon);
}
+ private static Icon getIcon(Notification notification) {
+ if (notification.shouldUseAppIcon()) {
+ return notification.getAppIcon();
+ }
+ return notification.getSmallIcon();
+ }
+
/**
* @return whether two ImageViews have the same colorFilterSet or none at all
*/
protected boolean hasSameColor(Object parentData, Object childData) {
- int parentColor = ((Notification) parentData).color;
- int childColor = ((Notification) childData).color;
+ int parentColor = getColor((Notification) parentData);
+ int childColor = getColor((Notification) childData);
return parentColor == childColor;
}
+ private static int getColor(Notification notification) {
+ if (notification.shouldUseAppIcon()) {
+ return 0; // the color filter isn't applied if using the app icon
+ }
+ return notification.color;
+ }
+
@Override
public boolean isEmpty(View view) {
return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 854ef92..bb26f92 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar;
import static android.app.Flags.keyguardPrivateNotifications;
+import static android.app.Flags.redactSensitiveContentNotificationsOnLockscreen;
import static android.app.StatusBarManager.ACTION_KEYGUARD_PRIVATE_NOTIFICATIONS_CHANGED;
import static android.app.StatusBarManager.EXTRA_KM_PRIVATE_NOTIFS_ALLOWED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
@@ -654,10 +655,12 @@
!userAllowsPrivateNotificationsInPublic(mCurrentUserId);
boolean isNotifForManagedProfile = mCurrentManagedProfiles.contains(userId);
boolean isNotifUserRedacted = !userAllowsPrivateNotificationsInPublic(userId);
+ boolean isNotifSensitive = redactSensitiveContentNotificationsOnLockscreen()
+ && ent.getRanking() != null && ent.getRanking().hasSensitiveContent();
- // redact notifications if the current user is redacting notifications; however if the
- // notification is associated with a managed profile, we rely on the managed profile
- // setting to determine whether to redact it
+ // redact notifications if the current user is redacting notifications or the notification
+ // contains sensitive content. However if the notification is associated with a managed
+ // profile, we rely on the managed profile setting to determine whether to redact it.
boolean isNotifRedacted = (!isNotifForManagedProfile && isCurrentUserRedactingNotifs)
|| isNotifUserRedacted;
@@ -666,10 +669,11 @@
boolean userForcesRedaction = packageHasVisibilityOverride(ent.getSbn().getKey());
if (keyguardPrivateNotifications()) {
- return !mKeyguardAllowingNotifications
- || userForcesRedaction || notificationRequestsRedaction && isNotifRedacted;
+ return !mKeyguardAllowingNotifications || isNotifSensitive
+ || userForcesRedaction || (notificationRequestsRedaction && isNotifRedacted);
} else {
- return userForcesRedaction || notificationRequestsRedaction && isNotifRedacted;
+ return userForcesRedaction || isNotifSensitive
+ || (notificationRequestsRedaction && isNotifRedacted);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 455c964..2e87a5b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -143,6 +143,12 @@
private var managedUserHandle: UserHandle? = null
private var mSplitShadeEnabled = false
+ var suppressDisconnects = false
+ set(value) {
+ field = value
+ disconnect()
+ }
+
// TODO(b/202758428): refactor so that we can test color updates via region samping, similar to
// how we test color updates when theme changes (See testThemeChangeUpdatesTextColor).
@@ -522,6 +528,7 @@
*/
fun disconnect() {
if (!smartspaceViews.isEmpty()) return
+ if (suppressDisconnects) return
execution.assertIsMainThread()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index c643238..682a9ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -34,8 +34,8 @@
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
-import com.android.systemui.statusbar.notification.row.NotificationContentInflaterLogger
import com.android.systemui.statusbar.notification.row.NotificationContentView
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinderLogger
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
@@ -44,30 +44,29 @@
import javax.inject.Inject
/** Populates additional information in conversation notifications */
-class ConversationNotificationProcessor @Inject constructor(
+class ConversationNotificationProcessor
+@Inject
+constructor(
private val launcherApps: LauncherApps,
private val conversationNotificationManager: ConversationNotificationManager
) {
fun processNotification(
- entry: NotificationEntry,
- recoveredBuilder: Notification.Builder,
- logger: NotificationContentInflaterLogger
+ entry: NotificationEntry,
+ recoveredBuilder: Notification.Builder,
+ logger: NotificationRowContentBinderLogger
): Notification.MessagingStyle? {
val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return null
messagingStyle.conversationType =
- if (entry.ranking.channel.isImportantConversation)
- Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
- else
- Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
+ if (entry.ranking.channel.isImportantConversation)
+ Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
+ else Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
entry.ranking.conversationShortcutInfo?.let { shortcutInfo ->
logger.logAsyncTaskProgress(entry, "getting shortcut icon")
messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo)
- shortcutInfo.label?.let { label ->
- messagingStyle.conversationTitle = label
- }
+ shortcutInfo.label?.let { label -> messagingStyle.conversationTitle = label }
}
messagingStyle.unreadMessageCount =
- conversationNotificationManager.getUnreadCount(entry, recoveredBuilder)
+ conversationNotificationManager.getUnreadCount(entry, recoveredBuilder)
return messagingStyle
}
}
@@ -77,7 +76,9 @@
* animations to conserve CPU and memory.
*/
@SysUISingleton
-class AnimatedImageNotificationManager @Inject constructor(
+class AnimatedImageNotificationManager
+@Inject
+constructor(
private val notifCollection: CommonNotifCollection,
private val bindEventManager: BindEventManager,
private val headsUpManager: HeadsUpManager,
@@ -88,17 +89,21 @@
/** Begins listening to state changes and updating animations accordingly. */
fun bind() {
- headsUpManager.addListener(object : OnHeadsUpChangedListener {
- override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
- updateAnimatedImageDrawables(entry)
+ headsUpManager.addListener(
+ object : OnHeadsUpChangedListener {
+ override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
+ updateAnimatedImageDrawables(entry)
+ }
}
- })
- statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
- override fun onExpandedChanged(isExpanded: Boolean) {
- isStatusBarExpanded = isExpanded
- notifCollection.allNotifs.forEach(::updateAnimatedImageDrawables)
+ )
+ statusBarStateController.addCallback(
+ object : StatusBarStateController.StateListener {
+ override fun onExpandedChanged(isExpanded: Boolean) {
+ isStatusBarExpanded = isExpanded
+ notifCollection.allNotifs.forEach(::updateAnimatedImageDrawables)
+ }
}
- })
+ )
bindEventManager.addListener(::updateAnimatedImageDrawables)
}
@@ -108,74 +113,73 @@
}
private fun updateAnimatedImageDrawables(row: ExpandableNotificationRow, animating: Boolean) =
- (row.layouts?.asSequence() ?: emptySequence())
- .flatMap { layout -> layout.allViews.asSequence() }
- .flatMap { view ->
- (view as? ConversationLayout)?.messagingGroups?.asSequence()
- ?: (view as? MessagingLayout)?.messagingGroups?.asSequence()
- ?: emptySequence()
- }
- .flatMap { messagingGroup -> messagingGroup.messageContainer.children }
- .mapNotNull { view ->
- (view as? MessagingImageMessage)
- ?.let { imageMessage ->
- imageMessage.drawable as? AnimatedImageDrawable
- }
- }
- .forEach { animatedImageDrawable ->
- if (animating) animatedImageDrawable.start()
- else animatedImageDrawable.stop()
- }
+ (row.layouts?.asSequence() ?: emptySequence())
+ .flatMap { layout -> layout.allViews.asSequence() }
+ .flatMap { view ->
+ (view as? ConversationLayout)?.messagingGroups?.asSequence()
+ ?: (view as? MessagingLayout)?.messagingGroups?.asSequence() ?: emptySequence()
+ }
+ .flatMap { messagingGroup -> messagingGroup.messageContainer.children }
+ .mapNotNull { view ->
+ (view as? MessagingImageMessage)?.let { imageMessage ->
+ imageMessage.drawable as? AnimatedImageDrawable
+ }
+ }
+ .forEach { animatedImageDrawable ->
+ if (animating) animatedImageDrawable.start() else animatedImageDrawable.stop()
+ }
}
/**
* Tracks state related to conversation notifications, and updates the UI of existing notifications
* when necessary.
+ *
* TODO(b/214083332) Refactor this class to use the right coordinators and controllers
*/
@SysUISingleton
-class ConversationNotificationManager @Inject constructor(
+class ConversationNotificationManager
+@Inject
+constructor(
bindEventManager: BindEventManager,
private val context: Context,
private val notifCollection: CommonNotifCollection,
@Main private val mainHandler: Handler
) {
// Need this state to be thread safe, since it's accessed from the ui thread
- // (NotificationEntryListener) and a bg thread (NotificationContentInflater)
+ // (NotificationEntryListener) and a bg thread (NotificationRowContentBinder)
private val states = ConcurrentHashMap<String, ConversationState>()
private var notifPanelCollapsed = true
private fun updateNotificationRanking(rankingMap: RankingMap) {
fun getLayouts(view: NotificationContentView) =
- sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild)
+ sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild)
val ranking = Ranking()
- val activeConversationEntries = states.keys.asSequence()
- .mapNotNull { notifCollection.getEntry(it) }
+ val activeConversationEntries =
+ states.keys.asSequence().mapNotNull { notifCollection.getEntry(it) }
for (entry in activeConversationEntries) {
if (rankingMap.getRanking(entry.sbn.key, ranking) && ranking.isConversation) {
val important = ranking.channel.isImportantConversation
var changed = false
- entry.row?.layouts?.asSequence()
- ?.flatMap(::getLayouts)
- ?.mapNotNull { it as? ConversationLayout }
- ?.filterNot { it.isImportantConversation == important }
- ?.forEach { layout ->
- changed = true
- if (important && entry.isMarkedForUserTriggeredMovement) {
- // delay this so that it doesn't animate in until after
- // the notif has been moved in the shade
- mainHandler.postDelayed(
- {
- layout.setIsImportantConversation(
- important,
- true)
- },
- IMPORTANCE_ANIMATION_DELAY.toLong())
- } else {
- layout.setIsImportantConversation(important, false)
- }
+ entry.row
+ ?.layouts
+ ?.asSequence()
+ ?.flatMap(::getLayouts)
+ ?.mapNotNull { it as? ConversationLayout }
+ ?.filterNot { it.isImportantConversation == important }
+ ?.forEach { layout ->
+ changed = true
+ if (important && entry.isMarkedForUserTriggeredMovement) {
+ // delay this so that it doesn't animate in until after
+ // the notif has been moved in the shade
+ mainHandler.postDelayed(
+ { layout.setIsImportantConversation(important, true) },
+ IMPORTANCE_ANIMATION_DELAY.toLong()
+ )
+ } else {
+ layout.setIsImportantConversation(important, false)
}
+ }
}
}
}
@@ -192,9 +196,7 @@
}
entry.row?.setOnExpansionChangedListener { isExpanded ->
if (entry.row?.isShown == true && isExpanded) {
- entry.row.performOnIntrinsicHeightReached {
- updateCount(isExpanded)
- }
+ entry.row.performOnIntrinsicHeightReached { updateCount(isExpanded) }
} else {
updateCount(isExpanded)
}
@@ -203,31 +205,38 @@
}
init {
- notifCollection.addCollectionListener(object : NotifCollectionListener {
- override fun onRankingUpdate(ranking: RankingMap) =
- updateNotificationRanking(ranking)
+ notifCollection.addCollectionListener(
+ object : NotifCollectionListener {
+ override fun onRankingUpdate(ranking: RankingMap) =
+ updateNotificationRanking(ranking)
- override fun onEntryRemoved(entry: NotificationEntry, reason: Int) =
- removeTrackedEntry(entry)
- })
+ override fun onEntryRemoved(entry: NotificationEntry, reason: Int) =
+ removeTrackedEntry(entry)
+ }
+ )
bindEventManager.addListener(::onEntryViewBound)
}
private fun ConversationState.shouldIncrementUnread(newBuilder: Notification.Builder) =
- if (notification.flags and Notification.FLAG_ONLY_ALERT_ONCE != 0) {
- false
- } else {
- val oldBuilder = Notification.Builder.recoverBuilder(context, notification)
- Notification.areStyledNotificationsVisiblyDifferent(oldBuilder, newBuilder)
- }
+ if (notification.flags and Notification.FLAG_ONLY_ALERT_ONCE != 0) {
+ false
+ } else {
+ val oldBuilder = Notification.Builder.recoverBuilder(context, notification)
+ Notification.areStyledNotificationsVisiblyDifferent(oldBuilder, newBuilder)
+ }
fun getUnreadCount(entry: NotificationEntry, recoveredBuilder: Notification.Builder): Int =
- states.compute(entry.key) { _, state ->
- val newCount = state?.run {
- if (shouldIncrementUnread(recoveredBuilder)) unreadCount + 1 else unreadCount
- } ?: 1
+ states
+ .compute(entry.key) { _, state ->
+ val newCount =
+ state?.run {
+ if (shouldIncrementUnread(recoveredBuilder)) unreadCount + 1
+ else unreadCount
+ }
+ ?: 1
ConversationState(newCount, entry.sbn.notification)
- }!!.unreadCount
+ }!!
+ .unreadCount
fun onNotificationPanelExpandStateChanged(isCollapsed: Boolean) {
notifPanelCollapsed = isCollapsed
@@ -235,18 +244,17 @@
// When the notification panel is expanded, reset the counters of any expanded
// conversations
- val expanded = states
+ val expanded =
+ states
.asSequence()
.mapNotNull { (key, _) ->
notifCollection.getEntry(key)?.let { entry ->
- if (entry.row?.isExpanded == true) key to entry
- else null
+ if (entry.row?.isExpanded == true) key to entry else null
}
}
.toMap()
states.replaceAll { key, state ->
- if (expanded.contains(key)) state.copy(unreadCount = 0)
- else state
+ if (expanded.contains(key)) state.copy(unreadCount = 0) else state
}
// Update UI separate from the replaceAll call, since ConcurrentHashMap may re-run the
// lambda if threads are in contention.
@@ -262,16 +270,16 @@
}
private fun resetBadgeUi(row: ExpandableNotificationRow): Unit =
- (row.layouts?.asSequence() ?: emptySequence())
- .flatMap { layout -> layout.allViews.asSequence() }
- .mapNotNull { view -> view as? ConversationLayout }
- .forEach { convoLayout -> convoLayout.setUnreadCount(0) }
+ (row.layouts?.asSequence() ?: emptySequence())
+ .flatMap { layout -> layout.allViews.asSequence() }
+ .mapNotNull { view -> view as? ConversationLayout }
+ .forEach { convoLayout -> convoLayout.setUnreadCount(0) }
private data class ConversationState(val unreadCount: Int, val notification: Notification)
companion object {
private const val IMPORTANCE_ANIMATION_DELAY =
- StackStateAnimator.ANIMATION_DURATION_STANDARD +
+ StackStateAnimator.ANIMATION_DURATION_STANDARD +
StackStateAnimator.ANIMATION_DURATION_PRIORITY_CHANGE +
100
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java
index 57b41f3..cafe6ffc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicChildBindController.java
@@ -16,8 +16,8 @@
package com.android.systemui.statusbar.notification;
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_CONTRACTED;
-import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_EXPANDED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index 98109f9..fc47dc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -23,7 +23,7 @@
import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
import javax.inject.Inject;
@@ -100,9 +100,9 @@
requireBinder().releaseViews(entry);
}
- private NotificationContentInflater.InflationCallback wrapInflationCallback(
+ private NotificationRowContentBinder.InflationCallback wrapInflationCallback(
InflationCallback callback) {
- return new NotificationContentInflater.InflationCallback() {
+ return new NotificationRowContentBinder.InflationCallback() {
@Override
public void handleInflationException(
NotificationEntry entry,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 42bf4e7..071192b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -289,8 +289,9 @@
// for each change, lookup the new value
.map {
secureSettings.getIntForUser(
- Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
- UserHandle.USER_CURRENT,
+ name = Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS,
+ def = 0,
+ userHandle = UserHandle.USER_CURRENT,
) == 1
}
// don't emit anything if nothing has changed
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
index 319b499..16d0cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt
@@ -18,25 +18,27 @@
import android.app.Notification
import android.content.Context
+import android.graphics.drawable.Drawable
import com.android.systemui.statusbar.StatusBarIconView
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.contentDescForNotification
import javax.inject.Inject
-/**
- * Testable wrapper around Context.
- */
-class IconBuilder @Inject constructor(
- private val context: Context
-) {
+/** Testable wrapper around Context. */
+class IconBuilder @Inject constructor(private val context: Context) {
fun createIconView(entry: NotificationEntry): StatusBarIconView {
return StatusBarIconView(
- context,
- "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}",
- entry.sbn)
+ context,
+ "${entry.sbn.packageName}/0x${Integer.toHexString(entry.sbn.id)}",
+ entry.sbn
+ )
}
fun getIconContentDescription(n: Notification): CharSequence {
return contentDescForNotification(context, n)
}
+
+ fun getAppIcon(n: Notification): Drawable {
+ return n.loadHeaderAppIcon(context)
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
index 271b0a8..3df9374 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt
@@ -20,6 +20,8 @@
import android.app.Notification.MessagingStyle
import android.app.Person
import android.content.pm.LauncherApps
+import android.graphics.drawable.AdaptiveIconDrawable
+import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.os.Build
import android.os.Bundle
@@ -165,7 +167,7 @@
Log.wtf(
TAG,
"Updating using the cache is not supported when the " +
- "notifications_background_conversation_icons flag is off"
+ "notifications_background_icons flag is off"
)
}
if (!usingCache || !Flags.notificationsBackgroundIcons()) {
@@ -216,39 +218,85 @@
@Throws(InflationException::class)
private fun getIconDescriptor(entry: NotificationEntry, redact: Boolean): StatusBarIcon {
- val n = entry.sbn.notification
val showPeopleAvatar = !redact && isImportantConversation(entry)
+ // If the descriptor is already cached, return it
+ getCachedIconDescriptor(entry, showPeopleAvatar)?.also {
+ return it
+ }
+
+ val n = entry.sbn.notification
+ var usingMonochromeAppIcon = false
+ val icon: Icon?
+ if (showPeopleAvatar) {
+ icon = createPeopleAvatar(entry)
+ } else if (android.app.Flags.notificationsUseMonochromeAppIcon()) {
+ if (n.shouldUseAppIcon()) {
+ icon =
+ getMonochromeAppIcon(entry)?.also { usingMonochromeAppIcon = true }
+ ?: n.smallIcon
+ } else {
+ icon = n.smallIcon
+ }
+ } else {
+ icon = n.smallIcon
+ }
+
+ if (icon == null) {
+ throw InflationException("No icon in notification from ${entry.sbn.packageName}")
+ }
+
+ val sbi = icon.toStatusBarIcon(entry)
+ cacheIconDescriptor(entry, sbi, showPeopleAvatar, usingMonochromeAppIcon)
+ return sbi
+ }
+
+ private fun getCachedIconDescriptor(
+ entry: NotificationEntry,
+ showPeopleAvatar: Boolean
+ ): StatusBarIcon? {
val peopleAvatarDescriptor = entry.icons.peopleAvatarDescriptor
+ val appIconDescriptor = entry.icons.appIconDescriptor
val smallIconDescriptor = entry.icons.smallIconDescriptor
// If cached, return corresponding cached values
- if (showPeopleAvatar && peopleAvatarDescriptor != null) {
- return peopleAvatarDescriptor
- } else if (!showPeopleAvatar && smallIconDescriptor != null) {
- return smallIconDescriptor
+ return when {
+ showPeopleAvatar && peopleAvatarDescriptor != null -> peopleAvatarDescriptor
+ android.app.Flags.notificationsUseMonochromeAppIcon() && appIconDescriptor != null ->
+ appIconDescriptor
+ smallIconDescriptor != null -> smallIconDescriptor
+ else -> null
}
+ }
- val icon =
- (if (showPeopleAvatar) {
- createPeopleAvatar(entry)
- } else {
- n.smallIcon
- })
- ?: throw InflationException("No icon in notification from " + entry.sbn.packageName)
-
- val sbi = icon.toStatusBarIcon(entry)
-
- // Cache if important conversation or app icon.
- if (isImportantConversation(entry) || android.app.Flags.notificationsUseAppIcon()) {
+ private fun cacheIconDescriptor(
+ entry: NotificationEntry,
+ descriptor: StatusBarIcon,
+ showPeopleAvatar: Boolean,
+ usingMonochromeAppIcon: Boolean
+ ) {
+ if (android.app.Flags.notificationsUseAppIcon() ||
+ android.app.Flags.notificationsUseMonochromeAppIcon()
+ ) {
+ // If either of the new icon flags is enabled, we cache the icon all the time.
if (showPeopleAvatar) {
- entry.icons.peopleAvatarDescriptor = sbi
+ entry.icons.peopleAvatarDescriptor = descriptor
+ } else if (usingMonochromeAppIcon) {
+ // When notificationsUseMonochromeAppIcon is enabled, we use the appIconDescriptor.
+ entry.icons.appIconDescriptor = descriptor
} else {
- entry.icons.smallIconDescriptor = sbi
+ // When notificationsUseAppIcon is enabled, the app icon overrides the small icon.
+ // But either way, it's a good idea to cache the descriptor.
+ entry.icons.smallIconDescriptor = descriptor
+ }
+ } else if (isImportantConversation(entry)) {
+ // Old approach: cache only if important conversation.
+ if (showPeopleAvatar) {
+ entry.icons.peopleAvatarDescriptor = descriptor
+ } else {
+ entry.icons.smallIconDescriptor = descriptor
}
}
-
- return sbi
}
@Throws(InflationException::class)
@@ -276,6 +324,29 @@
)
}
+ // TODO(b/335211019): Should we merge this with the method in GroupHelper?
+ private fun getMonochromeAppIcon(entry: NotificationEntry): Icon? {
+ // TODO(b/335211019): This should be done in the background.
+ var monochromeIcon: Icon? = null
+ try {
+ val appIcon: Drawable = iconBuilder.getAppIcon(entry.sbn.notification)
+ if (appIcon is AdaptiveIconDrawable) {
+ if (appIcon.monochrome != null) {
+ monochromeIcon =
+ Icon.createWithResourceAdaptiveDrawable(
+ /* resPackage = */ entry.sbn.packageName,
+ /* resId = */ appIcon.sourceDrawableResId,
+ /* useMonochrome = */ true,
+ /* inset = */ -3.0f * AdaptiveIconDrawable.getExtraInsetFraction()
+ )
+ }
+ }
+ } catch (e: Exception) {
+ Log.e(TAG, "Failed to getAppIcon() in getMonochromeAppIcon()", e)
+ }
+ return monochromeIcon
+ }
+
private suspend fun getLauncherShortcutIconForPeopleAvatar(entry: NotificationEntry) =
withContext(bgCoroutineContext) {
var icon: Icon? = null
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
index 442c097..d029ce7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconPack.java
@@ -33,6 +33,7 @@
@Nullable private final StatusBarIconView mAodIcon;
@Nullable private StatusBarIcon mSmallIconDescriptor;
+ @Nullable private StatusBarIcon mAppIconDescriptor;
@Nullable private StatusBarIcon mPeopleAvatarDescriptor;
private boolean mIsImportantConversation;
@@ -111,6 +112,15 @@
mPeopleAvatarDescriptor = peopleAvatarDescriptor;
}
+ @Nullable
+ StatusBarIcon getAppIconDescriptor() {
+ return mAppIconDescriptor;
+ }
+
+ void setAppIconDescriptor(@Nullable StatusBarIcon appIconDescriptor) {
+ mAppIconDescriptor = appIconDescriptor;
+ }
+
boolean isImportantConversation() {
return mIsImportantConversation;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt
index c74c396..c29d700 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/AvalancheProvider.kt
@@ -21,9 +21,9 @@
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.util.time.SystemClock
import javax.inject.Inject
// Class to track avalanche trigger event time.
@@ -33,6 +33,7 @@
constructor(
private val broadcastDispatcher: BroadcastDispatcher,
private val logger: VisualInterruptionDecisionLogger,
+ private val uiEventLogger: UiEventLogger,
) {
val TAG = "AvalancheProvider"
val timeoutMs = 120000
@@ -56,6 +57,7 @@
return
}
Log.d(TAG, "broadcastReceiver received intent.action=" + intent.action)
+ uiEventLogger.log(AvalancheSuppressor.AvalancheEvent.START);
startTime = System.currentTimeMillis()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
index 938a71f..42a5bdf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/CommonVisualInterruptionSuppressors.kt
@@ -33,6 +33,8 @@
import android.provider.Settings
import android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED
import android.provider.Settings.Global.HEADS_UP_OFF
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEvent;
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
@@ -241,12 +243,12 @@
override fun shouldSuppress(entry: NotificationEntry) =
keyguardNotificationVisibilityProvider.shouldHideNotification(entry)
}
-
class AvalancheSuppressor(
private val avalancheProvider: AvalancheProvider,
private val systemClock: SystemClock,
private val systemSettings: SystemSettings,
private val packageManager: PackageManager,
+ private val uiEventLogger: UiEventLogger,
) :
VisualInterruptionFilter(
types = setOf(PEEK, PULSE),
@@ -266,6 +268,44 @@
SUPPRESS
}
+ enum class AvalancheEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "An avalanche event occurred but this notification was suppressed by a " +
+ "non-avalanche suppressor.")
+ START(1802),
+
+ @UiEvent(doc = "HUN was suppressed in avalanche.")
+ SUPPRESS(1803),
+
+ @UiEvent(doc = "HUN allowed during avalanche because it is high priority.")
+ ALLOW_CONVERSATION_AFTER_AVALANCHE(1804),
+
+ @UiEvent(doc = "HUN allowed during avalanche because it is a high priority conversation.")
+ ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME(1805),
+
+ @UiEvent(doc = "HUN allowed during avalanche because it is a call.")
+ ALLOW_CALLSTYLE(1806),
+
+ @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.")
+ ALLOW_CATEGORY_REMINDER(1807),
+
+ @UiEvent(doc = "HUN allowed during avalanche because it is a calendar notification.")
+ ALLOW_CATEGORY_EVENT(1808),
+
+ @UiEvent(doc = "HUN allowed during avalanche because it has a full screen intent and " +
+ "the full screen intent permission is granted.")
+ ALLOW_FSI_WITH_PERMISSION_ON(1809),
+
+ @UiEvent(doc = "HUN allowed during avalanche because it is colorized.")
+ ALLOW_COLORIZED(1810),
+
+ @UiEvent(doc = "HUN allowed during avalanche because it is an emergency notification.")
+ ALLOW_EMERGENCY(1811);
+
+ override fun getId(): Int {
+ return id
+ }
+ }
+
override fun shouldSuppress(entry: NotificationEntry): Boolean {
if (!isCooldownEnabled()) {
return false
@@ -287,41 +327,46 @@
entry.ranking.isConversation &&
entry.sbn.notification.getWhen() > avalancheProvider.startTime
) {
+ uiEventLogger.log(AvalancheEvent.ALLOW_CONVERSATION_AFTER_AVALANCHE)
return State.ALLOW_CONVERSATION_AFTER_AVALANCHE
}
if (entry.channel?.isImportantConversation == true) {
+ uiEventLogger.log(AvalancheEvent.ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME)
return State.ALLOW_HIGH_PRIORITY_CONVERSATION_ANY_TIME
}
if (entry.sbn.notification.isStyle(Notification.CallStyle::class.java)) {
+ uiEventLogger.log(AvalancheEvent.ALLOW_CALLSTYLE)
return State.ALLOW_CALLSTYLE
}
if (entry.sbn.notification.category == CATEGORY_REMINDER) {
+ uiEventLogger.log(AvalancheEvent.ALLOW_CATEGORY_REMINDER)
return State.ALLOW_CATEGORY_REMINDER
}
if (entry.sbn.notification.category == CATEGORY_EVENT) {
+ uiEventLogger.log(AvalancheEvent.ALLOW_CATEGORY_EVENT)
return State.ALLOW_CATEGORY_EVENT
}
if (entry.sbn.notification.fullScreenIntent != null) {
+ uiEventLogger.log(AvalancheEvent.ALLOW_FSI_WITH_PERMISSION_ON)
return State.ALLOW_FSI_WITH_PERMISSION_ON
}
-
if (entry.sbn.notification.isColorized) {
- return State.ALLOW_COLORIZED
- }
- if (entry.sbn.notification.isColorized) {
+ uiEventLogger.log(AvalancheEvent.ALLOW_COLORIZED)
return State.ALLOW_COLORIZED
}
if (
packageManager.checkPermission(RECEIVE_EMERGENCY_BROADCAST, entry.sbn.packageName) ==
PERMISSION_GRANTED
) {
+ uiEventLogger.log(AvalancheEvent.ALLOW_EMERGENCY)
return State.ALLOW_EMERGENCY
}
+ uiEventLogger.log(AvalancheEvent.SUPPRESS)
return State.SUPPRESS
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
index 7e16cd5..84f8662 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImpl.kt
@@ -178,7 +178,8 @@
if (NotificationAvalancheSuppression.isEnabled) {
addFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+ AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ uiEventLogger)
)
avalancheProvider.register()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
index 0fdf681..1efa56f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryMonitor.kt
@@ -20,8 +20,6 @@
import android.util.Log
import com.android.systemui.CoreStartable
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
import dagger.Lazy
import javax.inject.Inject
@@ -30,7 +28,6 @@
class NotificationMemoryMonitor
@Inject
constructor(
- private val featureFlags: FeatureFlags,
private val notificationMemoryDumper: NotificationMemoryDumper,
private val notificationMemoryLogger: Lazy<NotificationMemoryLogger>,
) : CoreStartable {
@@ -42,9 +39,6 @@
override fun start() {
Log.d(TAG, "NotificationMemoryMonitor initialized.")
notificationMemoryDumper.init()
- if (featureFlags.isEnabled(Flags.NOTIFICATION_MEMORY_LOGGING_ENABLED)) {
- Log.d(TAG, "Notification memory logging enabled.")
- notificationMemoryLogger.get().init()
- }
+ notificationMemoryLogger.get().init()
}
}
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 99ce454..190a2cd 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
@@ -548,7 +548,7 @@
* and ensuring that the view is freed when it is safe to remove.
*
* @param inflationFlag flag corresponding to the content view to be freed
- * @deprecated By default, {@link NotificationContentInflater#unbindContent} will tell the
+ * @deprecated By default, {@link NotificationRowContentBinder#unbindContent} will tell the
* view hierarchy to only free when the view is safe to remove so this method is no longer
* needed. Will remove when all uses are gone.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index 2f03871..6ba26d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -92,7 +92,7 @@
private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
private final HeadsUpStyleProvider mHeadsUpStyleProvider;
- private final NotificationContentInflaterLogger mLogger;
+ private final NotificationRowContentBinderLogger mLogger;
@Inject
NotificationContentInflater(
@@ -104,7 +104,7 @@
SmartReplyStateInflater smartRepliesInflater,
NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
HeadsUpStyleProvider headsUpStyleProvider,
- NotificationContentInflaterLogger logger) {
+ NotificationRowContentBinderLogger logger) {
mRemoteViewCache = remoteViewCache;
mRemoteInputManager = remoteInputManager;
mConversationProcessor = conversationProcessor;
@@ -345,7 +345,7 @@
Context packageContext,
InflatedSmartReplyState previousSmartReplyState,
SmartReplyStateInflater inflater,
- NotificationContentInflaterLogger logger) {
+ NotificationRowContentBinderLogger logger) {
boolean inflateContracted = (reInflateFlags & FLAG_CONTENT_VIEW_CONTRACTED) != 0
&& result.newContentView != null;
boolean inflateExpanded = (reInflateFlags & FLAG_CONTENT_VIEW_EXPANDED) != 0
@@ -377,7 +377,7 @@
ExpandableNotificationRow row,
NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
HeadsUpStyleProvider headsUpStyleProvider,
- NotificationContentInflaterLogger logger) {
+ NotificationRowContentBinderLogger logger) {
return TraceUtils.trace("NotificationContentInflater.createRemoteViews", () -> {
InflationProgress result = new InflationProgress();
final NotificationEntry entryForLogging = row.getEntry();
@@ -465,7 +465,7 @@
ExpandableNotificationRow row,
RemoteViews.InteractionHandler remoteViewClickHandler,
@Nullable InflationCallback callback,
- NotificationContentInflaterLogger logger) {
+ NotificationRowContentBinderLogger logger) {
Trace.beginAsyncSection(APPLY_TRACE_METHOD, System.identityHashCode(row));
NotificationContentView privateLayout = row.getPrivateLayout();
@@ -676,7 +676,7 @@
NotificationViewWrapper existingWrapper,
final HashMap<Integer, CancellationSignal> runningInflations,
ApplyCallback applyCallback,
- NotificationContentInflaterLogger logger) {
+ NotificationRowContentBinderLogger logger) {
RemoteViews newContentView = applyCallback.getRemoteView();
if (inflateSynchronously) {
try {
@@ -845,7 +845,7 @@
private static void handleInflationError(
HashMap<Integer, CancellationSignal> runningInflations, Exception e,
NotificationEntry notification, @Nullable InflationCallback callback,
- NotificationContentInflaterLogger logger, String logContext) {
+ NotificationRowContentBinderLogger logger, String logContext) {
Assert.isMainThread();
logger.logAsyncTaskException(notification, logContext, e);
runningInflations.values().forEach(CancellationSignal::cancel);
@@ -864,7 +864,7 @@
@InflationFlag int reInflateFlags, NotifRemoteViewCache remoteViewCache,
HashMap<Integer, CancellationSignal> runningInflations,
@Nullable InflationCallback endListener, NotificationEntry entry,
- ExpandableNotificationRow row, NotificationContentInflaterLogger logger) {
+ ExpandableNotificationRow row, NotificationRowContentBinderLogger logger) {
Assert.isMainThread();
if (!runningInflations.isEmpty()) {
return false;
@@ -1080,7 +1080,7 @@
private final SmartReplyStateInflater mSmartRepliesInflater;
private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider;
private final HeadsUpStyleProvider mHeadsUpStyleProvider;
- private final NotificationContentInflaterLogger mLogger;
+ private final NotificationRowContentBinderLogger mLogger;
private AsyncInflationTask(
Executor inflationExecutor,
@@ -1099,7 +1099,7 @@
SmartReplyStateInflater smartRepliesInflater,
NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider,
HeadsUpStyleProvider headsUpStyleProvider,
- NotificationContentInflaterLogger logger) {
+ NotificationRowContentBinderLogger logger) {
mEntry = entry;
mRow = row;
mInflationExecutor = inflationExecutor;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index 33339a7..c1302a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -20,6 +20,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import java.lang.annotation.Retention;
@@ -72,6 +74,10 @@
@NonNull ExpandableNotificationRow row,
@InflationFlag int contentToUnbind);
+ /** For testing, ensure all inflation is synchronous. */
+ @VisibleForTesting
+ void setInflateSynchronously(boolean inflateSynchronously);
+
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true,
prefix = {"FLAG_CONTENT_VIEW_"},
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderLogger.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
rename to packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderLogger.kt
index 15c7055..a32e1d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinderLogger.kt
@@ -32,7 +32,7 @@
import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
import javax.inject.Inject
-class NotificationContentInflaterLogger
+class NotificationRowContentBinderLogger
@Inject
constructor(@NotifInflationLog private val buffer: LogBuffer) {
fun logNotBindingRowWasRemoved(entry: NotificationEntry) {
@@ -158,4 +158,4 @@
}
}
-private const val TAG = "NotificationContentInflater"
+private const val TAG = "NotificationRowContentBinder"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
index bae89fb..427fb66 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -35,7 +35,7 @@
/**
* Content views that are out of date and need to be rebound.
*
- * TODO: This should go away once {@link NotificationContentInflater} is broken down into
+ * TODO: This should go away once {@link NotificationRowContentBinder} is broken down into
* smaller stages as then the stage itself would be invalidated.
*/
private @InflationFlag int mDirtyContentViews = mContentViews;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
index 3fce9ce..6fc82c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflater.kt
@@ -342,7 +342,7 @@
reinflateFlags: Int,
entry: NotificationEntry,
context: Context,
- logger: NotificationContentInflaterLogger,
+ logger: NotificationRowContentBinderLogger,
): HybridNotificationView? {
if (AsyncHybridViewInflation.isUnexpectedlyInLegacyMode()) return null
if (reinflateFlags and FLAG_CONTENT_VIEW_SINGLE_LINE == 0) {
@@ -354,7 +354,7 @@
var view: HybridNotificationView? = null
- traceSection("NotificationContentInflater#inflateSingleLineView") {
+ traceSection("SingleLineViewInflater#inflateSingleLineView") {
val inflater = LayoutInflater.from(context)
val layoutRes: Int =
if (isConversation)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index bd7f766..d1fabb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -191,8 +191,12 @@
updateTransformedTypes();
addRemainingTransformTypes();
updateCropToPaddingForImageViews();
- Notification notification = row.getEntry().getSbn().getNotification();
- mIcon.setTag(ImageTransformState.ICON_TAG, notification.getSmallIcon());
+ Notification n = row.getEntry().getSbn().getNotification();
+ if (n.shouldUseAppIcon()) {
+ mIcon.setTag(ImageTransformState.ICON_TAG, n.getAppIcon());
+ } else {
+ mIcon.setTag(ImageTransformState.ICON_TAG, n.getSmallIcon());
+ }
// We need to reset all views that are no longer transforming in case a view was previously
// transformed, but now we decided to transform its container instead.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
index d6c73a9..2dccea6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt
@@ -16,24 +16,22 @@
package com.android.systemui.statusbar.notification.shared
-import com.android.systemui.Flags
import com.android.systemui.flags.FlagToken
-import com.android.systemui.flags.RefactorFlagUtils
/** Helper for reading or using the heads-up cycling flag state. */
@Suppress("NOTHING_TO_INLINE")
object NotificationHeadsUpCycling {
- /** The aconfig flag name - enable this feature when FLAG_NOTIFICATION_THROTTLE_HUN is on. */
- const val FLAG_NAME = Flags.FLAG_NOTIFICATION_THROTTLE_HUN
+ /** The aconfig flag name */
+ const val FLAG_NAME = NotificationThrottleHun.FLAG_NAME
/** A token used for dependency declaration */
val token: FlagToken
- get() = FlagToken(FLAG_NAME, isEnabled)
+ get() = NotificationThrottleHun.token
/** Is the heads-up cycling animation enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.notificationThrottleHun()
+ get() = NotificationThrottleHun.isEnabled
/** Whether to animate the bottom line when transiting from a tall HUN to a short HUN */
@JvmStatic
@@ -46,13 +44,12 @@
* build to ensure that the refactor author catches issues in testing.
*/
@JvmStatic
- inline fun isUnexpectedlyInLegacyMode() =
- RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+ inline fun isUnexpectedlyInLegacyMode() = NotificationThrottleHun.isUnexpectedlyInLegacyMode()
/**
* Called to ensure code is only run when the flag is disabled. This will throw an exception if
* the flag is enabled to ensure that the refactor author catches issues in testing.
*/
@JvmStatic
- inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+ inline fun assertInLegacyMode() = NotificationThrottleHun.assertInLegacyMode()
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt
index dd81d42..71f0de0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationThrottleHun.kt
@@ -24,7 +24,7 @@
@Suppress("NOTHING_TO_INLINE")
object NotificationThrottleHun {
/** The aconfig flag name */
- const val FLAG_NAME = Flags.FLAG_NOTIFICATION_THROTTLE_HUN
+ const val FLAG_NAME = Flags.FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN
/** A token used for dependency declaration */
val token: FlagToken
@@ -33,7 +33,7 @@
/** Is the refactor enabled */
@JvmStatic
inline val isEnabled
- get() = Flags.notificationThrottleHun()
+ get() = Flags.notificationAvalancheThrottleHun()
/**
* Called to ensure code is only run when the flag is enabled. This protects users from the
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
index db544ce..f6722a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationPlaceholderRepository.kt
@@ -38,9 +38,6 @@
*/
val shadeScrimBounds = MutableStateFlow<ShadeScrimBounds?>(null)
- /** the y position of the top of the HUN area */
- val headsUpTop = MutableStateFlow(0f)
-
/** height made available to the notifications in the size-constrained mode of lock screen. */
val constrainedAvailableSpace = MutableStateFlow(0)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
index afcf3ae..8557afc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt
@@ -79,9 +79,6 @@
*/
val scrolledToTop: StateFlow<Boolean> = placeholderRepository.scrolledToTop.asStateFlow()
- /** The y-coordinate in px of bottom of the contents of the HUN. */
- val headsUpTop: StateFlow<Float> = placeholderRepository.headsUpTop.asStateFlow()
-
/**
* The amount in px that the notification stack should scroll due to internal expansion. This
* should only happen when a notification expansion hits the bottom of the screen, so it is
@@ -125,8 +122,4 @@
fun setConstrainedAvailableSpace(height: Int) {
placeholderRepository.constrainedAvailableSpace.value = height
}
-
- fun setHeadsUpTop(headsUpTop: Float) {
- placeholderRepository.headsUpTop.value = headsUpTop
- }
}
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 a99fbfc..e90a64a 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
@@ -139,8 +139,6 @@
*/
val scrolledToTop: Flow<Boolean> =
stackAppearanceInteractor.scrolledToTop.dumpValue("scrolledToTop")
- /** The y-coordinate in px of bottom of the contents of the HUN. */
- val headsUpTop: Flow<Float> = stackAppearanceInteractor.headsUpTop.dumpValue("headsUpTop")
/** Receives the amount (px) that the stack should scroll due to internal expansion. */
val syntheticScrollConsumer: (Float) -> Unit = stackAppearanceInteractor::setSyntheticScroll
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index ea33be0..634bd7e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -60,10 +60,6 @@
interactor.setConstrainedAvailableSpace(height)
}
- fun onHeadsUpTopChanged(headsUpTop: Float) {
- interactor.setHeadsUpTop(headsUpTop)
- }
-
/** Sets the content alpha for the current state of the brightness mirror */
fun setAlphaForBrightnessMirror(alpha: Float) {
interactor.setAlphaForBrightnessMirror(alpha)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index b448d85..fc29eab 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -413,6 +413,7 @@
afterKeyguardGone: Boolean,
customMessage: String?,
) {
+ Log.i(TAG, "Invoking dismissKeyguardThenExecute, afterKeyguardGone: $afterKeyguardGone")
if (
!action.willRunAnimationOnKeyguard() &&
wakefulnessLifecycle.wakefulness == WakefulnessLifecycle.WAKEFULNESS_ASLEEP &&
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index ebb62ec..db4f0af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -112,6 +112,7 @@
import kotlinx.coroutines.Job;
import java.io.PrintWriter;
+import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Objects;
@@ -899,6 +900,11 @@
} finally {
Trace.endSection();
}
+ } else {
+ Log.w(TAG, "Ignoring request to dismiss, dumping state: ");
+ StringWriter sw = new StringWriter();
+ mKeyguardStateController.dump(new PrintWriter(sw), null);
+ Log.w(TAG, sw.toString());
}
updateStates();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
index c32f0e8..261258a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryImpl.kt
@@ -40,6 +40,7 @@
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.dump.DumpManager
import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.log.table.logDiffsForTable
@@ -99,6 +100,7 @@
private val context: Context,
@Background private val bgDispatcher: CoroutineDispatcher,
@Application private val scope: CoroutineScope,
+ @Main private val mainDispatcher: CoroutineDispatcher,
airplaneModeRepository: AirplaneModeRepository,
// Some "wifi networks" should be rendered as a mobile connection, which is why the wifi
// repository is an input to the mobile repository.
@@ -315,6 +317,7 @@
trySend(false)
awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
}
+ .flowOn(mainDispatcher)
.logDiffsForTable(
tableLogger,
LOGGING_PREFIX,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
index 4325897..1449e53 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImpl.kt
@@ -23,6 +23,7 @@
import android.telephony.satellite.SatelliteManager
import android.telephony.satellite.SatelliteManager.SATELLITE_RESULT_SUCCESS
import android.telephony.satellite.SatelliteModemStateCallback
+import android.telephony.satellite.SatelliteSupportedStateCallback
import androidx.annotation.VisibleForTesting
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -35,6 +36,7 @@
import com.android.systemui.statusbar.pipeline.dagger.DeviceBasedSatelliteInputLog
import com.android.systemui.statusbar.pipeline.dagger.VerboseDeviceBasedSatelliteInputLog
import com.android.systemui.statusbar.pipeline.satellite.data.RealDeviceBasedSatelliteRepository
+import com.android.systemui.statusbar.pipeline.satellite.data.prod.DeviceBasedSatelliteRepositoryImpl.Companion.POLLING_INTERVAL_MS
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Companion.whenSupported
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.NotSupported
import com.android.systemui.statusbar.pipeline.satellite.data.prod.SatelliteSupport.Supported
@@ -162,60 +164,6 @@
@get:VisibleForTesting
val satelliteSupport: MutableStateFlow<SatelliteSupport> = MutableStateFlow(Unknown)
- init {
- satelliteManager = satelliteManagerOpt.getOrNull()
-
- isSatelliteAllowedForCurrentLocation = MutableStateFlow(false)
-
- if (satelliteManager != null) {
- // First, check that satellite is supported on this device
- scope.launch {
- val waitTime = ensureMinUptime(systemClock, MIN_UPTIME)
- if (waitTime > 0) {
- logBuffer.i({ long1 = waitTime }) {
- "Waiting $long1 ms before checking for satellite support"
- }
- delay(waitTime)
- }
-
- satelliteSupport.value = satelliteManager.checkSatelliteSupported()
-
- logBuffer.i(
- { str1 = satelliteSupport.value.toString() },
- { "Checked for system support. support=$str1" },
- )
-
- // We only need to check location availability if this mode is supported
- if (satelliteSupport.value is Supported) {
- isSatelliteAllowedForCurrentLocation.subscriptionCount
- .map { it > 0 }
- .distinctUntilChanged()
- .collectLatest { hasSubscribers ->
- if (hasSubscribers) {
- /*
- * As there is no listener available for checking satellite allowed,
- * we must poll. Defaulting to polling at most once every hour while
- * active. Subsequent OOS events will restart the job, so a flaky
- * connection might cause more frequent checks.
- */
- while (true) {
- logBuffer.i {
- "requestIsCommunicationAllowedForCurrentLocation"
- }
- checkIsSatelliteAllowed()
- delay(POLLING_INTERVAL_MS)
- }
- }
- }
- }
- }
- } else {
- logBuffer.i { "Satellite manager is null" }
-
- satelliteSupport.value = NotSupported
- }
- }
-
/**
* Note that we are given an "unbound" [TelephonyManager] (meaning it was not created with a
* specific `subscriptionId`). Therefore this is the radio power state of the
@@ -269,6 +217,134 @@
}
.onStart { emit(Unit) }
+ init {
+ satelliteManager = satelliteManagerOpt.getOrNull()
+
+ isSatelliteAllowedForCurrentLocation = MutableStateFlow(false)
+
+ if (satelliteManager != null) {
+ // Outer scope launch allows us to delay until MIN_UPTIME
+ scope.launch {
+ // First, check that satellite is supported on this device
+ satelliteSupport.value = checkSatelliteSupportAfterMinUptime(satelliteManager)
+ logBuffer.i(
+ { str1 = satelliteSupport.value.toString() },
+ { "Checked for system support. support=$str1" },
+ )
+
+ // Second, launch a job to poll for service availability based on location
+ scope.launch { pollForAvailabilityBasedOnLocation() }
+
+ // Third, register a listener to let us know if there are changes to support
+ scope.launch { listenForChangesToSatelliteSupport(satelliteManager) }
+ }
+ } else {
+ logBuffer.i { "Satellite manager is null" }
+ satelliteSupport.value = NotSupported
+ }
+ }
+
+ private suspend fun checkSatelliteSupportAfterMinUptime(
+ sm: SatelliteManager
+ ): SatelliteSupport {
+ val waitTime = ensureMinUptime(systemClock, MIN_UPTIME)
+ if (waitTime > 0) {
+ logBuffer.i({ long1 = waitTime }) {
+ "Waiting $long1 ms before checking for satellite support"
+ }
+ delay(waitTime)
+ }
+
+ return sm.checkSatelliteSupported()
+ }
+
+ /*
+ * As there is no listener available for checking satellite allowed, we must poll the service.
+ * Defaulting to polling at most once every 20m while active. Subsequent OOS events will restart
+ * the job, so a flaky connection might cause more frequent checks.
+ */
+ private suspend fun pollForAvailabilityBasedOnLocation() {
+ satelliteSupport
+ .whenSupported(
+ supported = ::isSatelliteAllowedHasListener,
+ orElse = flowOf(false),
+ retrySignal = telephonyProcessCrashedEvent,
+ )
+ .collectLatest { hasSubscribers ->
+ if (hasSubscribers) {
+ while (true) {
+ logBuffer.i { "requestIsCommunicationAllowedForCurrentLocation" }
+ checkIsSatelliteAllowed()
+ delay(POLLING_INTERVAL_MS)
+ }
+ }
+ }
+ }
+
+ /**
+ * Register a callback with [SatelliteManager] to let us know if there is a change in satellite
+ * support. This job restarts if there is a crash event detected.
+ *
+ * Note that the structure of this method looks similar to [whenSupported], but since we want
+ * this callback registered even when it is [NotSupported], we just mimic the structure here.
+ */
+ private suspend fun listenForChangesToSatelliteSupport(sm: SatelliteManager) {
+ telephonyProcessCrashedEvent.collectLatest {
+ satelliteIsSupportedCallback.collect { supported ->
+ if (supported) {
+ satelliteSupport.value = Supported(sm)
+ } else {
+ satelliteSupport.value = NotSupported
+ }
+ }
+ }
+ }
+
+ /**
+ * Callback version of [checkSatelliteSupported]. This flow should be retried on the same
+ * [telephonyProcessCrashedEvent] signal, but does not require a [SupportedSatelliteManager],
+ * since it specifically watches for satellite support.
+ */
+ private val satelliteIsSupportedCallback: Flow<Boolean> =
+ if (satelliteManager == null) {
+ flowOf(false)
+ } else {
+ conflatedCallbackFlow {
+ val callback = SatelliteSupportedStateCallback { supported ->
+ logBuffer.i {
+ "onSatelliteSupportedStateChanged: " +
+ "${if (supported) "supported" else "not supported"}"
+ }
+ trySend(supported)
+ }
+
+ var registered = false
+ try {
+ satelliteManager.registerForSupportedStateChanged(
+ bgDispatcher.asExecutor(),
+ callback
+ )
+ registered = true
+ } catch (e: Exception) {
+ logBuffer.e("error registering for supported state change", e)
+ }
+
+ awaitClose {
+ if (registered) {
+ satelliteManager.unregisterForSupportedStateChanged(callback)
+ }
+ }
+ }
+ }
+
+ /**
+ * Signal that we should start polling [checkIsSatelliteAllowed]. We only need to poll if there
+ * are active listeners to [isSatelliteAllowedForCurrentLocation]
+ */
+ @SuppressWarnings("unused")
+ private fun isSatelliteAllowedHasListener(sm: SupportedSatelliteManager): Flow<Boolean> =
+ isSatelliteAllowedForCurrentLocation.subscriptionCount.map { it > 0 }.distinctUntilChanged()
+
override val connectionState =
satelliteSupport
.whenSupported(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
index eb09e6e..a972985 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt
@@ -17,6 +17,8 @@
import android.util.Log
import androidx.annotation.VisibleForTesting
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
import com.android.systemui.Dumpable
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dump.DumpManager
@@ -35,6 +37,7 @@
@Inject
constructor(
dumpManager: DumpManager,
+ private val uiEventLogger: UiEventLogger
) : Dumpable {
private val tag = "AvalancheController"
@@ -65,6 +68,21 @@
// For debugging only
@VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet()
+ enum class ThrottleEvent(private val id: Int) : UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "HUN was shown.")
+ SHOWN(1812),
+
+ @UiEvent(doc = "HUN was dropped to show higher priority HUNs.")
+ DROPPED(1813),
+
+ @UiEvent(doc = "HUN was removed while waiting to show.")
+ REMOVED(1814);
+
+ override fun getId(): Int {
+ return id
+ }
+ }
+
init {
dumpManager.registerNormalDumpable(tag, /* module */ this)
}
@@ -145,6 +163,7 @@
log { "$fn => remove from next" }
if (entry in nextMap) nextMap.remove(entry)
if (entry in nextList) nextList.remove(entry)
+ uiEventLogger.log(ThrottleEvent.REMOVED)
} else if (entry in debugDropSet) {
log { "$fn => remove from dropset" }
debugDropSet.remove(entry)
@@ -268,6 +287,7 @@
private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) {
log { "SHOW: " + getKey(entry) }
+ uiEventLogger.log(ThrottleEvent.SHOWN)
headsUpEntryShowing = entry
runnableList.forEach {
@@ -295,6 +315,12 @@
// Remove runnable labels for dropped huns
val listToDrop = nextList.subList(1, nextList.size)
+
+ // Log dropped HUNs
+ for (e in listToDrop) {
+ uiEventLogger.log(ThrottleEvent.DROPPED)
+ }
+
if (debug) {
// Clear runnable labels
for (e in listToDrop) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
index b07aa81..d210e93 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java
@@ -18,13 +18,16 @@
import android.app.IActivityTaskManager;
+import com.android.systemui.Dumpable;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.policy.KeyguardStateController.Callback;
+import java.io.PrintWriter;
+
/**
* Source of truth for keyguard state: If locked, occluded, has password, trusted etc.
*/
-public interface KeyguardStateController extends CallbackController<Callback> {
+public interface KeyguardStateController extends CallbackController<Callback>, Dumpable {
/**
* If the device is locked or unlocked.
@@ -40,6 +43,8 @@
return isShowing() && !isOccluded();
}
+ default void dump(PrintWriter pw, String[] args) { }
+
/**
* If the keyguard is showing. This includes when it's occluded by an activity, and when
* the device is asleep or in always on mode, except when the screen timed out and the user
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 886010c..c256e64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -36,7 +36,6 @@
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.keyguard.logging.KeyguardUpdateMonitorLogger;
-import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
@@ -57,7 +56,7 @@
*
*/
@SysUISingleton
-public class KeyguardStateControllerImpl implements KeyguardStateController, Dumpable {
+public class KeyguardStateControllerImpl implements KeyguardStateController {
private static final boolean DEBUG_AUTH_WITH_ADB = false;
private static final String AUTH_BROADCAST_KEY = "debug_trigger_auth";
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
deleted file mode 100644
index 2cad844..0000000
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.java
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util.concurrency;
-
-import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
-
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Process;
-
-import com.android.systemui.Flags;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.dagger.qualifiers.BroadcastRunning;
-import com.android.systemui.dagger.qualifiers.LongRunning;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dagger.qualifiers.NotifInflation;
-
-import dagger.Module;
-import dagger.Provides;
-
-import java.util.concurrent.Executor;
-
-import javax.inject.Named;
-
-/**
- * Dagger Module for classes found within the concurrent package.
- */
-@Module
-public abstract class SysUIConcurrencyModule {
-
- // Slow BG executor can potentially affect UI if UI is waiting for an updated state from this
- // thread
- private static final Long BG_SLOW_DISPATCH_THRESHOLD = 1000L;
- private static final Long BG_SLOW_DELIVERY_THRESHOLD = 1000L;
- private static final Long LONG_SLOW_DISPATCH_THRESHOLD = 2500L;
- private static final Long LONG_SLOW_DELIVERY_THRESHOLD = 2500L;
- private static final Long BROADCAST_SLOW_DISPATCH_THRESHOLD = 1000L;
- private static final Long BROADCAST_SLOW_DELIVERY_THRESHOLD = 1000L;
- private static final Long NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD = 1000L;
- private static final Long NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD = 1000L;
-
- /** Background Looper */
- @Provides
- @SysUISingleton
- @Background
- public static Looper provideBgLooper() {
- HandlerThread thread = new HandlerThread("SysUiBg",
- Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
- thread.getLooper().setSlowLogThresholdMs(BG_SLOW_DISPATCH_THRESHOLD,
- BG_SLOW_DELIVERY_THRESHOLD);
- return thread.getLooper();
- }
-
- /** BroadcastRunning Looper (for sending and receiving broadcasts) */
- @Provides
- @SysUISingleton
- @BroadcastRunning
- public static Looper provideBroadcastRunningLooper() {
- HandlerThread thread = new HandlerThread("BroadcastRunning",
- Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
- thread.getLooper().setSlowLogThresholdMs(BROADCAST_SLOW_DISPATCH_THRESHOLD,
- BROADCAST_SLOW_DELIVERY_THRESHOLD);
- return thread.getLooper();
- }
-
- /** Long running tasks Looper */
- @Provides
- @SysUISingleton
- @LongRunning
- public static Looper provideLongRunningLooper() {
- HandlerThread thread = new HandlerThread("SysUiLng",
- Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
- thread.getLooper().setSlowLogThresholdMs(LONG_SLOW_DISPATCH_THRESHOLD,
- LONG_SLOW_DELIVERY_THRESHOLD);
- return thread.getLooper();
- }
-
- /** Notification inflation Looper */
- @Provides
- @SysUISingleton
- @NotifInflation
- public static Looper provideNotifInflationLooper(@Background Looper bgLooper) {
- if (!Flags.dedicatedNotifInflationThread()) {
- return bgLooper;
- }
-
- final HandlerThread thread = new HandlerThread("NotifInflation",
- Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
- final Looper looper = thread.getLooper();
- looper.setSlowLogThresholdMs(NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD,
- NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD);
- return looper;
- }
-
- /**
- * Background Handler.
- *
- * Prefer the Background Executor when possible.
- */
- @Provides
- @Background
- public static Handler provideBgHandler(@Background Looper bgLooper) {
- return new Handler(bgLooper);
- }
-
- /**
- * Provide a BroadcastRunning Executor (for sending and receiving broadcasts).
- */
- @Provides
- @SysUISingleton
- @BroadcastRunning
- public static Executor provideBroadcastRunningExecutor(@BroadcastRunning Looper looper) {
- return new ExecutorImpl(looper);
- }
-
- /**
- * Provide a Long running Executor.
- */
- @Provides
- @SysUISingleton
- @LongRunning
- public static Executor provideLongRunningExecutor(@LongRunning Looper looper) {
- return new ExecutorImpl(looper);
- }
-
- /**
- * Provide a Long running Executor.
- */
- @Provides
- @SysUISingleton
- @LongRunning
- public static DelayableExecutor provideLongRunningDelayableExecutor(
- @LongRunning Looper looper) {
- return new ExecutorImpl(looper);
- }
-
- /**
- * Provide a Background-Thread Executor.
- */
- @Provides
- @SysUISingleton
- @Background
- public static Executor provideBackgroundExecutor(@Background Looper looper) {
- return new ExecutorImpl(looper);
- }
-
- /**
- * Provide a Background-Thread Executor.
- */
- @Provides
- @SysUISingleton
- @Background
- public static DelayableExecutor provideBackgroundDelayableExecutor(@Background Looper looper) {
- return new ExecutorImpl(looper);
- }
-
- /**
- * Provide a Background-Thread Executor.
- */
- @Provides
- @SysUISingleton
- @Background
- public static RepeatableExecutor provideBackgroundRepeatableExecutor(
- @Background DelayableExecutor exec) {
- return new RepeatableExecutorImpl(exec);
- }
-
- /**
- * Provide a Main-Thread Executor.
- */
- @Provides
- @SysUISingleton
- @Main
- public static RepeatableExecutor provideMainRepeatableExecutor(@Main DelayableExecutor exec) {
- return new RepeatableExecutorImpl(exec);
- }
-
- /** */
- @Provides
- @Main
- public static MessageRouter providesMainMessageRouter(
- @Main DelayableExecutor executor) {
- return new MessageRouterImpl(executor);
- }
-
- /** */
- @Provides
- @Background
- public static MessageRouter providesBackgroundMessageRouter(
- @Background DelayableExecutor executor) {
- return new MessageRouterImpl(executor);
- }
-
- /** */
- @Provides
- @SysUISingleton
- @Named(TIME_TICK_HANDLER_NAME)
- public static Handler provideTimeTickHandler() {
- HandlerThread thread = new HandlerThread("TimeTick");
- thread.start();
- return new Handler(thread.getLooper());
- }
-
- /** */
- @Provides
- @SysUISingleton
- @NotifInflation
- public static Executor provideNotifInflationExecutor(@NotifInflation Looper looper) {
- return new ExecutorImpl(looper);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt
new file mode 100644
index 0000000..a7abb6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/SysUIConcurrencyModule.kt
@@ -0,0 +1,234 @@
+/*
+ * 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.util.concurrency
+
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Process
+import android.view.Choreographer
+import com.android.systemui.Dependency
+import com.android.systemui.Flags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.BroadcastRunning
+import com.android.systemui.dagger.qualifiers.LongRunning
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.NotifInflation
+import dagger.Module
+import dagger.Provides
+import java.util.concurrent.Executor
+import javax.inject.Named
+import javax.inject.Qualifier
+
+@Qualifier
+@MustBeDocumented
+@Retention(AnnotationRetention.RUNTIME)
+annotation class BackPanelUiThread
+
+/** Dagger Module for classes found within the concurrent package. */
+@Module
+object SysUIConcurrencyModule {
+ // Slow BG executor can potentially affect UI if UI is waiting for an updated state from this
+ // thread
+ private const val BG_SLOW_DISPATCH_THRESHOLD = 1000L
+ private const val BG_SLOW_DELIVERY_THRESHOLD = 1000L
+ private const val LONG_SLOW_DISPATCH_THRESHOLD = 2500L
+ private const val LONG_SLOW_DELIVERY_THRESHOLD = 2500L
+ private const val BROADCAST_SLOW_DISPATCH_THRESHOLD = 1000L
+ private const val BROADCAST_SLOW_DELIVERY_THRESHOLD = 1000L
+ private const val NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD = 1000L
+ private const val NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD = 1000L
+
+ /** Background Looper */
+ @Provides
+ @SysUISingleton
+ @Background
+ fun provideBgLooper(): Looper {
+ val thread = HandlerThread("SysUiBg", Process.THREAD_PRIORITY_BACKGROUND)
+ thread.start()
+ thread
+ .getLooper()
+ .setSlowLogThresholdMs(BG_SLOW_DISPATCH_THRESHOLD, BG_SLOW_DELIVERY_THRESHOLD)
+ return thread.getLooper()
+ }
+
+ /** BroadcastRunning Looper (for sending and receiving broadcasts) */
+ @Provides
+ @SysUISingleton
+ @BroadcastRunning
+ fun provideBroadcastRunningLooper(): Looper {
+ val thread = HandlerThread("BroadcastRunning", Process.THREAD_PRIORITY_BACKGROUND)
+ thread.start()
+ thread
+ .getLooper()
+ .setSlowLogThresholdMs(
+ BROADCAST_SLOW_DISPATCH_THRESHOLD,
+ BROADCAST_SLOW_DELIVERY_THRESHOLD
+ )
+ return thread.getLooper()
+ }
+
+ /** Long running tasks Looper */
+ @Provides
+ @SysUISingleton
+ @LongRunning
+ fun provideLongRunningLooper(): Looper {
+ val thread = HandlerThread("SysUiLng", Process.THREAD_PRIORITY_BACKGROUND)
+ thread.start()
+ thread
+ .getLooper()
+ .setSlowLogThresholdMs(LONG_SLOW_DISPATCH_THRESHOLD, LONG_SLOW_DELIVERY_THRESHOLD)
+ return thread.getLooper()
+ }
+
+ /** Notification inflation Looper */
+ @Provides
+ @SysUISingleton
+ @NotifInflation
+ fun provideNotifInflationLooper(@Background bgLooper: Looper): Looper {
+ if (!Flags.dedicatedNotifInflationThread()) {
+ return bgLooper
+ }
+ val thread = HandlerThread("NotifInflation", Process.THREAD_PRIORITY_BACKGROUND)
+ thread.start()
+ val looper = thread.getLooper()
+ looper.setSlowLogThresholdMs(
+ NOTIFICATION_INFLATION_SLOW_DISPATCH_THRESHOLD,
+ NOTIFICATION_INFLATION_SLOW_DELIVERY_THRESHOLD
+ )
+ return looper
+ }
+
+ @Provides
+ @SysUISingleton
+ @BackPanelUiThread
+ fun provideBackPanelUiThreadContext(
+ @Main mainLooper: Looper,
+ @Main mainHandler: Handler,
+ @Main mainExecutor: Executor
+ ): UiThreadContext {
+ return if (Flags.edgeBackGestureHandlerThread()) {
+ val thread =
+ HandlerThread("BackPanelUiThread", Process.THREAD_PRIORITY_DISPLAY).apply {
+ start()
+ looper.setSlowLogThresholdMs(
+ LONG_SLOW_DISPATCH_THRESHOLD,
+ LONG_SLOW_DELIVERY_THRESHOLD
+ )
+ }
+ UiThreadContext(
+ thread.looper,
+ thread.threadHandler,
+ thread.threadExecutor,
+ thread.threadHandler.runWithScissors { Choreographer.getInstance() }
+ )
+ } else {
+ UiThreadContext(
+ mainLooper,
+ mainHandler,
+ mainExecutor,
+ mainHandler.runWithScissors { Choreographer.getInstance() }
+ )
+ }
+ }
+
+ /**
+ * Background Handler.
+ *
+ * Prefer the Background Executor when possible.
+ */
+ @Provides
+ @Background
+ fun provideBgHandler(@Background bgLooper: Looper): Handler = Handler(bgLooper)
+
+ /** Provide a BroadcastRunning Executor (for sending and receiving broadcasts). */
+ @Provides
+ @SysUISingleton
+ @BroadcastRunning
+ fun provideBroadcastRunningExecutor(@BroadcastRunning looper: Looper): Executor =
+ ExecutorImpl(looper)
+
+ /** Provide a Long running Executor. */
+ @Provides
+ @SysUISingleton
+ @LongRunning
+ fun provideLongRunningExecutor(@LongRunning looper: Looper): Executor = ExecutorImpl(looper)
+
+ /** Provide a Long running Executor. */
+ @Provides
+ @SysUISingleton
+ @LongRunning
+ fun provideLongRunningDelayableExecutor(@LongRunning looper: Looper): DelayableExecutor =
+ ExecutorImpl(looper)
+
+ /** Provide a Background-Thread Executor. */
+ @Provides
+ @SysUISingleton
+ @Background
+ fun provideBackgroundExecutor(@Background looper: Looper): Executor = ExecutorImpl(looper)
+
+ /** Provide a Background-Thread Executor. */
+ @Provides
+ @SysUISingleton
+ @Background
+ fun provideBackgroundDelayableExecutor(@Background looper: Looper): DelayableExecutor =
+ ExecutorImpl(looper)
+
+ /** Provide a Background-Thread Executor. */
+ @Provides
+ @SysUISingleton
+ @Background
+ fun provideBackgroundRepeatableExecutor(
+ @Background exec: DelayableExecutor
+ ): RepeatableExecutor = RepeatableExecutorImpl(exec)
+
+ /** Provide a Main-Thread Executor. */
+ @Provides
+ @SysUISingleton
+ @Main
+ fun provideMainRepeatableExecutor(@Main exec: DelayableExecutor): RepeatableExecutor =
+ RepeatableExecutorImpl(exec)
+
+ /** */
+ @Provides
+ @Main
+ fun providesMainMessageRouter(@Main executor: DelayableExecutor): MessageRouter =
+ MessageRouterImpl(executor)
+
+ /** */
+ @Provides
+ @Background
+ fun providesBackgroundMessageRouter(@Background executor: DelayableExecutor): MessageRouter =
+ MessageRouterImpl(executor)
+
+ /** */
+ @Provides
+ @SysUISingleton
+ @Named(Dependency.TIME_TICK_HANDLER_NAME)
+ fun provideTimeTickHandler(): Handler {
+ val thread = HandlerThread("TimeTick")
+ thread.start()
+ return Handler(thread.getLooper())
+ }
+
+ /** */
+ @Provides
+ @SysUISingleton
+ @NotifInflation
+ fun provideNotifInflationExecutor(@NotifInflation looper: Looper): Executor =
+ ExecutorImpl(looper)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/UiThreadContext.kt b/packages/SystemUI/src/com/android/systemui/util/concurrency/UiThreadContext.kt
new file mode 100644
index 0000000..8c8c686
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/UiThreadContext.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.util.concurrency
+
+import android.os.Handler
+import android.os.Looper
+import android.view.Choreographer
+import com.android.systemui.util.Assert
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicReference
+
+private const val DEFAULT_TIMEOUT = 150L
+
+class UiThreadContext(
+ val looper: Looper,
+ val handler: Handler,
+ val executor: Executor,
+ val choreographer: Choreographer
+) {
+ fun isCurrentThread() {
+ Assert.isCurrentThread(looper)
+ }
+
+ fun <T> runWithScissors(block: () -> T): T {
+ return handler.runWithScissors(block)
+ }
+
+ fun runWithScissors(block: Runnable) {
+ handler.runWithScissors(block, DEFAULT_TIMEOUT)
+ }
+}
+
+fun <T> Handler.runWithScissors(block: () -> T): T {
+ val returnedValue = AtomicReference<T>()
+ runWithScissors({ returnedValue.set(block()) }, DEFAULT_TIMEOUT)
+ return returnedValue.get()!!
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
index c6b0dc5..154737c 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
@@ -17,17 +17,18 @@
package com.android.systemui.volume.domain.interactor
import android.bluetooth.BluetoothAdapter
+import android.content.Context
import android.media.AudioDeviceInfo
-import android.media.AudioDeviceInfo.TYPE_WIRED_HEADPHONES
-import android.media.AudioDeviceInfo.TYPE_WIRED_HEADSET
import com.android.settingslib.bluetooth.CachedBluetoothDevice
import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.media.BluetoothMediaDevice
import com.android.settingslib.media.MediaDevice
import com.android.settingslib.media.MediaDevice.MediaDeviceType
+import com.android.settingslib.media.PhoneMediaDevice
import com.android.settingslib.volume.data.repository.AudioRepository
import com.android.settingslib.volume.data.repository.AudioSharingRepository
import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.volume.domain.model.AudioOutputDevice
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
@@ -38,6 +39,7 @@
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
@@ -49,6 +51,7 @@
class AudioOutputInteractor
@Inject
constructor(
+ @Application private val context: Context,
audioRepository: AudioRepository,
audioModeInteractor: AudioModeInteractor,
@VolumePanelScope scope: CoroutineScope,
@@ -60,7 +63,7 @@
audioSharingRepository: AudioSharingRepository,
) {
- val currentAudioDevice: Flow<AudioOutputDevice> =
+ val currentAudioDevice: StateFlow<AudioOutputDevice> =
audioModeInteractor.isOngoingCall
.flatMapLatest { isOngoingCall ->
if (isOngoingCall) {
@@ -81,30 +84,32 @@
val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing
private fun AudioDeviceInfo.toAudioOutputDevice(): AudioOutputDevice {
- if (type == TYPE_WIRED_HEADPHONES || type == TYPE_WIRED_HEADSET) {
+ if (
+ BluetoothAdapter.checkBluetoothAddress(address) &&
+ localBluetoothManager != null &&
+ bluetoothAdapter != null
+ ) {
+ val remoteDevice = bluetoothAdapter.getRemoteDevice(address)
+ localBluetoothManager.cachedDeviceManager.findDevice(remoteDevice)?.let {
+ device: CachedBluetoothDevice ->
+ return AudioOutputDevice.Bluetooth(
+ name = device.name,
+ icon = deviceIconInteractor.loadIcon(device),
+ cachedBluetoothDevice = device,
+ )
+ }
+ }
+ // Built-in device has an empty address
+ if (address.isNotEmpty()) {
return AudioOutputDevice.Wired(
name = productName.toString(),
icon = deviceIconInteractor.loadIcon(type),
)
}
- val cachedBluetoothDevice: CachedBluetoothDevice? =
- if (address.isEmpty() || localBluetoothManager == null || bluetoothAdapter == null) {
- null
- } else {
- val remoteDevice = bluetoothAdapter.getRemoteDevice(address)
- localBluetoothManager.cachedDeviceManager.findDevice(remoteDevice)
- }
- return cachedBluetoothDevice?.let {
- AudioOutputDevice.Bluetooth(
- name = it.name,
- icon = deviceIconInteractor.loadIcon(it),
- cachedBluetoothDevice = it,
- )
- }
- ?: AudioOutputDevice.BuiltIn(
- name = productName.toString(),
- icon = deviceIconInteractor.loadIcon(type),
- )
+ return AudioOutputDevice.BuiltIn(
+ name = PhoneMediaDevice.getMediaTransferThisDeviceName(context),
+ icon = deviceIconInteractor.loadIcon(type),
+ )
}
private fun MediaDevice.toAudioOutputDevice(): AudioOutputDevice {
@@ -115,7 +120,8 @@
icon = icon,
cachedBluetoothDevice = cachedDevice,
)
- deviceType == MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE ->
+ deviceType == MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE ||
+ deviceType == MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE ->
AudioOutputDevice.Wired(
name = name,
icon = icon,
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
index 0dc2647..79a4ae7 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/LocalMediaRepositoryFactory.kt
@@ -19,14 +19,13 @@
import com.android.settingslib.volume.data.repository.LocalMediaRepositoryImpl
import com.android.settingslib.volume.shared.AudioManagerEventsReceiver
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.media.controls.util.LocalMediaManagerFactory
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
interface LocalMediaRepositoryFactory {
- fun create(packageName: String?): LocalMediaRepository
+ fun create(packageName: String?, coroutineScope: CoroutineScope): LocalMediaRepository
}
@SysUISingleton
@@ -35,10 +34,12 @@
constructor(
private val eventsReceiver: AudioManagerEventsReceiver,
private val localMediaManagerFactory: LocalMediaManagerFactory,
- @Application private val coroutineScope: CoroutineScope,
) : LocalMediaRepositoryFactory {
- override fun create(packageName: String?): LocalMediaRepository =
+ override fun create(
+ packageName: String?,
+ coroutineScope: CoroutineScope
+ ): LocalMediaRepository =
LocalMediaRepositoryImpl(
eventsReceiver,
localMediaManagerFactory.create(packageName),
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
index 199bc3b..333f4ad 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputActionsInteractor.kt
@@ -21,7 +21,7 @@
import com.android.systemui.animation.DialogTransitionAnimator
import com.android.systemui.animation.Expandable
import com.android.systemui.media.dialog.MediaOutputDialogManager
-import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import javax.inject.Inject
@@ -29,14 +29,12 @@
@VolumePanelScope
class MediaOutputActionsInteractor
@Inject
-constructor(
- private val mediaOutputDialogManager: MediaOutputDialogManager,
-) {
+constructor(private val mediaOutputDialogManager: MediaOutputDialogManager) {
- fun onBarClick(sessionWithPlaybackState: SessionWithPlaybackState?, expandable: Expandable?) {
- if (sessionWithPlaybackState?.isPlaybackActive == true) {
+ fun onBarClick(model: MediaOutputComponentModel?, expandable: Expandable?) {
+ if (model is MediaOutputComponentModel.MediaSession) {
mediaOutputDialogManager.createAndShowWithController(
- sessionWithPlaybackState.session.packageName,
+ model.session.packageName,
false,
expandable?.dialogController()
)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
new file mode 100644
index 0000000..ed25129
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
@@ -0,0 +1,105 @@
+/*
+ * 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.mediaoutput.domain.interactor
+
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
+import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
+import com.android.systemui.volume.domain.model.AudioOutputDevice
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState
+import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
+import com.android.systemui.volume.panel.shared.model.Result
+import com.android.systemui.volume.panel.shared.model.filterData
+import com.android.systemui.volume.panel.shared.model.wrapInResult
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
+
+/** Gathers together a domain state for the Media Output Volume Panel component. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@VolumePanelScope
+class MediaOutputComponentInteractor
+@Inject
+constructor(
+ @VolumePanelScope private val coroutineScope: CoroutineScope,
+ private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
+ audioOutputInteractor: AudioOutputInteractor,
+ audioModeInteractor: AudioModeInteractor,
+ interactor: MediaOutputInteractor,
+) {
+
+ private val sessionWithPlaybackState: StateFlow<Result<SessionWithPlaybackState?>> =
+ interactor.defaultActiveMediaSession
+ .filterData()
+ .flatMapLatest { session ->
+ if (session == null) {
+ flowOf(null)
+ } else {
+ mediaDeviceSessionInteractor.playbackState(session).mapNotNull { playback ->
+ playback?.let { SessionWithPlaybackState(session, playback.isActive) }
+ }
+ }
+ }
+ .wrapInResult()
+ .stateIn(
+ coroutineScope,
+ SharingStarted.Eagerly,
+ Result.Loading(),
+ )
+
+ private val currentAudioDevice: Flow<AudioOutputDevice> =
+ audioOutputInteractor.currentAudioDevice.filter { it !is AudioOutputDevice.Unknown }
+
+ val mediaOutputModel: StateFlow<Result<MediaOutputComponentModel>> =
+ audioModeInteractor.isOngoingCall
+ .flatMapLatest { isOngoingCall ->
+ audioOutputInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing ->
+ if (isOngoingCall) {
+ currentAudioDevice.map {
+ MediaOutputComponentModel.Calling(it, isInAudioSharing)
+ }
+ } else {
+ combine(sessionWithPlaybackState.filterData(), currentAudioDevice) {
+ sessionWithPlaybackState,
+ currentAudioDevice ->
+ if (sessionWithPlaybackState == null) {
+ MediaOutputComponentModel.Idle(currentAudioDevice, isInAudioSharing)
+ } else {
+ MediaOutputComponentModel.MediaSession(
+ sessionWithPlaybackState.session,
+ sessionWithPlaybackState.isPlaybackActive,
+ currentAudioDevice,
+ isInAudioSharing,
+ )
+ }
+ }
+ }
+ }
+ }
+ .wrapInResult()
+ .stateIn(coroutineScope, SharingStarted.Eagerly, Result.Loading())
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
index 9fbd79a..31a8977 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputInteractor.kt
@@ -35,6 +35,7 @@
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@@ -46,6 +47,7 @@
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.withContext
/** Provides observable models about the current media session state. */
@@ -105,12 +107,9 @@
.filterData()
.map { it?.packageName }
.distinctUntilChanged()
- .map { localMediaRepositoryFactory.create(it) }
- .stateIn(
- coroutineScope,
- SharingStarted.Eagerly,
- localMediaRepositoryFactory.create(null)
- )
+ .transformLatest {
+ coroutineScope { emit(localMediaRepositoryFactory.create(it, this)) }
+ }
/** Currently connected [MediaDevice]. */
val currentConnectedDevice: Flow<MediaDevice?> =
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.kt
new file mode 100644
index 0000000..220fb2b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/model/MediaOutputComponentModel.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.volume.panel.component.mediaoutput.domain.model
+
+import com.android.systemui.volume.domain.model.AudioOutputDevice
+import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
+
+/** Models domain data for the Media Output Component */
+sealed interface MediaOutputComponentModel {
+
+ val device: AudioOutputDevice
+ val isInAudioSharing: Boolean
+
+ /** There is an ongoing call on the device. */
+ data class Calling(
+ override val device: AudioOutputDevice,
+ override val isInAudioSharing: Boolean,
+ ) : MediaOutputComponentModel
+
+ /** There is media playing on the device. */
+ data class MediaSession(
+ val session: MediaDeviceSession,
+ val isPlaybackActive: Boolean,
+ override val device: AudioOutputDevice,
+ override val isInAudioSharing: Boolean,
+ ) : MediaOutputComponentModel
+
+ /** There is nothing playing on the device. */
+ data class Idle(
+ override val device: AudioOutputDevice,
+ override val isInAudioSharing: Boolean,
+ ) : MediaOutputComponentModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
index 40b7977..36b42f2 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModel.kt
@@ -18,33 +18,24 @@
import android.content.Context
import com.android.internal.logging.UiEventLogger
-import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
import com.android.systemui.animation.Expandable
import com.android.systemui.common.shared.model.Color
import com.android.systemui.common.shared.model.Icon
import com.android.systemui.res.R
-import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
import com.android.systemui.volume.domain.model.AudioOutputDevice
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputActionsInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
-import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputComponentInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
import com.android.systemui.volume.panel.dagger.scope.VolumePanelScope
import com.android.systemui.volume.panel.shared.model.Result
import com.android.systemui.volume.panel.shared.model.filterData
-import com.android.systemui.volume.panel.shared.model.wrapInResult
import com.android.systemui.volume.panel.ui.VolumePanelUiEvent
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.flatMapLatest
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn
/** Models the UI of the Media Output Volume Panel component. */
@@ -56,61 +47,40 @@
private val context: Context,
@VolumePanelScope private val coroutineScope: CoroutineScope,
private val actionsInteractor: MediaOutputActionsInteractor,
- private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
- private val audioOutputInteractor: AudioOutputInteractor,
- audioModeInteractor: AudioModeInteractor,
- interactor: MediaOutputInteractor,
+ private val mediaOutputComponentInteractor: MediaOutputComponentInteractor,
private val uiEventLogger: UiEventLogger,
) {
- private val sessionWithPlaybackState: StateFlow<Result<SessionWithPlaybackState?>> =
- interactor.defaultActiveMediaSession
- .filterData()
- .flatMapLatest { session ->
- if (session == null) {
- flowOf(null)
- } else {
- mediaDeviceSessionInteractor.playbackState(session).mapNotNull { playback ->
- playback?.let { SessionWithPlaybackState(session, playback.isActive) }
- }
- }
- }
- .wrapInResult()
- .stateIn(
- coroutineScope,
- SharingStarted.Eagerly,
- Result.Loading(),
- )
-
val connectedDeviceViewModel: StateFlow<ConnectedDeviceViewModel?> =
- combine(
- sessionWithPlaybackState.filterData(),
- audioModeInteractor.isOngoingCall,
- audioOutputInteractor.currentAudioDevice.filter {
- it !is AudioOutputDevice.Unknown
- },
- audioOutputInteractor.isInAudioSharing,
- ) { mediaDeviceSession, isOngoingCall, currentConnectedDevice, isInAudioSharing ->
+ mediaOutputComponentInteractor.mediaOutputModel
+ .filterData()
+ .map { mediaOutputModel ->
val label =
- when {
- isOngoingCall -> context.getString(R.string.media_output_title_ongoing_call)
- mediaDeviceSession?.isPlaybackActive == true ->
- context.getString(
- R.string.media_output_label_title,
- mediaDeviceSession.session.appLabel
- )
- else -> context.getString(R.string.media_output_title_without_playing)
+ when (mediaOutputModel) {
+ is MediaOutputComponentModel.Idle -> {
+ context.getString(R.string.media_output_title_without_playing)
+ }
+ is MediaOutputComponentModel.MediaSession -> {
+ if (mediaOutputModel.isPlaybackActive) {
+ context.getString(
+ R.string.media_output_label_title,
+ mediaOutputModel.session.appLabel,
+ )
+ } else {
+ context.getString(R.string.media_output_title_without_playing)
+ }
+ }
+ is MediaOutputComponentModel.Calling -> {
+ context.getString(R.string.media_output_title_ongoing_call)
+ }
}
ConnectedDeviceViewModel(
label,
- when (isInAudioSharing) {
- true -> {
- context.getString(R.string.audio_sharing_description)
- }
- false -> {
- currentConnectedDevice.name
- }
- }
+ if (mediaOutputModel.isInAudioSharing) {
+ context.getString(R.string.audio_sharing_description)
+ } else {
+ mediaOutputModel.device.name
+ },
)
}
.stateIn(
@@ -120,16 +90,20 @@
)
val deviceIconViewModel: StateFlow<DeviceIconViewModel?> =
- combine(sessionWithPlaybackState.filterData(), audioOutputInteractor.currentAudioDevice) {
- mediaDeviceSession,
- currentConnectedDevice ->
+ mediaOutputComponentInteractor.mediaOutputModel
+ .filterData()
+ .map { mediaOutputModel ->
val icon: Icon =
- currentConnectedDevice
- .takeIf { currentConnectedDevice !is AudioOutputDevice.Unknown }
+ mediaOutputModel.device
+ .takeIf { it !is AudioOutputDevice.Unknown }
?.icon
?.let { Icon.Loaded(it, null) }
?: Icon.Resource(R.drawable.ic_media_home_devices, null)
- if (mediaDeviceSession?.isPlaybackActive == true) {
+ val isPlaybackActive =
+ (mediaOutputModel as? MediaOutputComponentModel.MediaSession)
+ ?.isPlaybackActive == true
+ val isCalling = mediaOutputModel is MediaOutputComponentModel.Calling
+ if (isPlaybackActive || isCalling) {
DeviceIconViewModel.IsPlaying(
icon = icon,
iconColor =
@@ -156,8 +130,9 @@
)
val enabled: StateFlow<Boolean> =
- audioOutputInteractor.isInAudioSharing
- .map { !it }
+ mediaOutputComponentInteractor.mediaOutputModel
+ .filterData()
+ .map { !it.isInAudioSharing }
.stateIn(
coroutineScope,
SharingStarted.Eagerly,
@@ -166,7 +141,11 @@
fun onBarClick(expandable: Expandable?) {
uiEventLogger.log(VolumePanelUiEvent.VOLUME_PANEL_MEDIA_OUTPUT_CLICKED)
- val result = sessionWithPlaybackState.value
- actionsInteractor.onBarClick((result as? Result.Data)?.data, expandable)
+ val result: Result<MediaOutputComponentModel> =
+ mediaOutputComponentInteractor.mediaOutputModel.value
+ actionsInteractor.onBarClick(
+ (result as? Result.Data<MediaOutputComponentModel>)?.data,
+ expandable
+ )
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
index 4b2d26a..6c6a1cc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/spatial/ui/viewmodel/SpatialAudioViewModel.kt
@@ -48,15 +48,31 @@
private val uiEventLogger: UiEventLogger,
) {
+ private val spatialSpeakerIcon =
+ Icon.Resource(R.drawable.ic_spatial_speaker, contentDescription = null)
+
val spatialAudioButton: StateFlow<ButtonViewModel?> =
- interactor.isEnabled
- .map {
- val isChecked = it is SpatialAudioEnabledModel.SpatialAudioEnabled
- it.toViewModel(isChecked)
+ combine(interactor.isEnabled, interactor.isAvailable) { isEnabled, isAvailable ->
+ isEnabled
+ .toViewModel(
+ isChecked = isEnabled is SpatialAudioEnabledModel.SpatialAudioEnabled,
+ isHeadTrackingAvailable =
+ isAvailable is SpatialAudioAvailabilityModel.SpatialAudio,
+ )
.copy(label = context.getString(R.string.volume_panel_spatial_audio_title))
}
.stateIn(scope, SharingStarted.Eagerly, null)
+ val shouldUsePopup: StateFlow<Boolean> =
+ interactor.isAvailable
+ .map {
+ // head tracking availability means there are three possible states for the spatial
+ // audio: disabled, enabled regular, enabled with head tracking.
+ // Show popup in this case instead of a togglealbe button.
+ it is SpatialAudioAvailabilityModel.SpatialAudio
+ }
+ .stateIn(scope, SharingStarted.Eagerly, false)
+
val isAvailable: StateFlow<Boolean> =
availabilityCriteria.isAvailable().stateIn(scope, SharingStarted.Eagerly, true)
@@ -73,8 +89,12 @@
}
}
.map { isEnabled ->
- val isChecked = isEnabled == currentIsEnabled
- val buttonViewModel: ButtonViewModel = isEnabled.toViewModel(isChecked)
+ val buttonViewModel: ButtonViewModel =
+ isEnabled.toViewModel(
+ isChecked = isEnabled == currentIsEnabled,
+ isHeadTrackingAvailable =
+ isAvailable is SpatialAudioAvailabilityModel.HeadTracking,
+ )
SpatialAudioButtonViewModel(button = buttonViewModel, model = isEnabled)
}
}
@@ -97,11 +117,21 @@
scope.launch { interactor.setEnabled(model) }
}
- private fun SpatialAudioEnabledModel.toViewModel(isChecked: Boolean): ButtonViewModel {
+ private fun SpatialAudioEnabledModel.toViewModel(
+ isChecked: Boolean,
+ isHeadTrackingAvailable: Boolean,
+ ): ButtonViewModel {
+ // This method deliberately uses the same icon for the case when head tracking is disabled
+ // to show a toggle button with a non-changing icon
if (this is SpatialAudioEnabledModel.HeadTrackingEnabled) {
return ButtonViewModel(
isActive = isChecked,
- icon = Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null),
+ icon =
+ if (isHeadTrackingAvailable) {
+ Icon.Resource(R.drawable.ic_head_tracking, contentDescription = null)
+ } else {
+ spatialSpeakerIcon
+ },
label = context.getString(R.string.volume_panel_spatial_audio_tracking)
)
}
@@ -109,7 +139,12 @@
if (this is SpatialAudioEnabledModel.SpatialAudioEnabled) {
return ButtonViewModel(
isActive = isChecked,
- icon = Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null),
+ icon =
+ if (isHeadTrackingAvailable) {
+ Icon.Resource(R.drawable.ic_spatial_audio, contentDescription = null)
+ } else {
+ spatialSpeakerIcon
+ },
label = context.getString(R.string.volume_panel_spatial_audio_fixed)
)
}
@@ -117,7 +152,12 @@
if (this is SpatialAudioEnabledModel.Disabled) {
return ButtonViewModel(
isActive = isChecked,
- icon = Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null),
+ icon =
+ if (isHeadTrackingAvailable) {
+ Icon.Resource(R.drawable.ic_spatial_audio_off, contentDescription = null)
+ } else {
+ spatialSpeakerIcon
+ },
label = context.getString(R.string.volume_panel_spatial_audio_off)
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
index fd01b48..850162e 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/slider/ui/viewmodel/AudioStreamSliderViewModel.kt
@@ -146,14 +146,18 @@
isEnabled = isEnabled,
a11yStep = volumeRange.step,
a11yClickDescription =
- context.getString(
- if (isMuted) {
- R.string.volume_panel_hint_unmute
- } else {
- R.string.volume_panel_hint_mute
- },
- label,
- ),
+ if (isAffectedByMute) {
+ context.getString(
+ if (isMuted) {
+ R.string.volume_panel_hint_unmute
+ } else {
+ R.string.volume_panel_hint_mute
+ },
+ label,
+ )
+ } else {
+ null
+ },
a11yStateDescription =
if (volume == volumeRange.first) {
context.getString(
diff --git a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
index 99f95648..3da725b 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/ui/navigation/VolumeNavigator.kt
@@ -98,12 +98,12 @@
private fun showNewVolumePanel() {
activityStarter.dismissKeyguardThenExecute(
- {
+ /* action = */ {
volumePanelGlobalStateInteractor.setVisible(true)
false
},
- {},
- true
+ /* cancel = */ {},
+ /* afterKeyguardGone = */ true,
)
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
index 8c4179d..0a3225e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java
@@ -161,8 +161,11 @@
doAnswer(this::checkMainThread).when(mKeyguardUpdateMonitor)
.removeCallback(any(KeyguardUpdateMonitorCallback.class));
- mCarrierTextCallbackInfo = new CarrierTextManager.CarrierTextCallbackInfo("",
- new CharSequence[]{}, false, new int[]{});
+ mCarrierTextCallbackInfo = new CarrierTextManager.CarrierTextCallbackInfo(
+ /* carrierText= */ "",
+ /* listOfCarriers= */ new CharSequence[]{},
+ /* anySimReady= */ false,
+ /* subscriptionIds= */ new int[]{});
when(mTelephonyManager.getSupportedModemCount()).thenReturn(3);
when(mTelephonyManager.getActiveModemCount()).thenReturn(3);
@@ -473,7 +476,7 @@
}
@Test
- public void carrierText_satelliteTextNull_notUsed() {
+ public void carrierText_satelliteTextNull_isSatelliteFalse_textNotUsed() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
list.add(TEST_SUBSCRIPTION);
@@ -491,12 +494,38 @@
CarrierTextManager.CarrierTextCallbackInfo.class);
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
- // THEN the default subscription carrier text is used
+ // THEN satellite mode is false and the default subscription carrier text is used
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().isInSatelliteMode).isFalse();
assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER);
}
@Test
+ public void carrierText_hasSatelliteText_isSatelliteTrue_textUsed() {
+ reset(mCarrierTextCallback);
+ List<SubscriptionInfo> list = new ArrayList<>();
+ list.add(TEST_SUBSCRIPTION);
+ when(mKeyguardUpdateMonitor.getSimState(anyInt())).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
+ when(mKeyguardUpdateMonitor.getFilteredSubscriptionInfo()).thenReturn(list);
+ mKeyguardUpdateMonitor.mServiceStates = new HashMap<>();
+
+ // WHEN the satellite text is non-null
+ mSatelliteViewModel.getCarrierText().setValue("Satellite Test Text");
+ mTestScope.getTestScheduler().runCurrent();
+
+ ArgumentCaptor<CarrierTextManager.CarrierTextCallbackInfo> captor =
+ ArgumentCaptor.forClass(
+ CarrierTextManager.CarrierTextCallbackInfo.class);
+ FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
+
+ // THEN satellite mode is true and the satellite text is used
+ verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().isInSatelliteMode).isTrue();
+ assertThat(captor.getValue().carrierText).isEqualTo("Satellite Test Text");
+ }
+
+ @Test
public void carrierText_satelliteTextUpdates_autoTriggersCallback() {
reset(mCarrierTextCallback);
List<SubscriptionInfo> list = new ArrayList<>();
@@ -517,6 +546,7 @@
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
// AND use the satellite text as the carrier text
+ assertThat(captor.getValue().isInSatelliteMode).isTrue();
assertThat(captor.getValue().carrierText).isEqualTo("Test satellite text");
// WHEN the satellite text is reset to null
@@ -528,6 +558,7 @@
// that doesn't include the satellite info
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().isInSatelliteMode).isFalse();
assertThat(captor.getValue().carrierText).isEqualTo(TEST_CARRIER);
}
@@ -566,10 +597,11 @@
mCarrierTextManager.setListening(mCarrierTextCallback);
// THEN we should automatically re-trigger #updateCarrierText and get callback info
- // that includes the new satellite text
+ // that includes the new satellite state and text
mTestScope.getTestScheduler().runCurrent();
FakeExecutor.exhaustExecutors(mMainExecutor, mBgExecutor);
verify(mCarrierTextCallback).updateCarrierInfo(captor.capture());
+ assertThat(captor.getValue().isInSatelliteMode).isTrue();
assertThat(captor.getValue().carrierText).isEqualTo("New satellite text");
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 1167fce..f46cfdc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -383,11 +383,25 @@
}
if (testCase.isFaceOnly) {
- val expectedIconAsset = R.raw.face_dialog_authenticating
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+ val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+ val expectedIconAsset =
+ if (shouldPulseAnimation!!) {
+ if (lastPulseLightToDark!!) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ } else {
+ R.drawable.face_dialog_pulse_dark_to_light
+ }
assertThat(iconAsset).isEqualTo(expectedIconAsset)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(true)
}
if (testCase.isCoex) {
@@ -409,11 +423,26 @@
}
} else {
// implicit flow
- val expectedIconAsset = R.raw.face_dialog_authenticating
+ val shouldRepeatAnimation by
+ collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+ val lastPulseLightToDark by collectLastValue(iconViewModel.lastPulseLightToDark)
+
+ val expectedIconAsset =
+ if (shouldPulseAnimation!!) {
+ if (lastPulseLightToDark!!) {
+ R.drawable.face_dialog_pulse_dark_to_light
+ } else {
+ R.drawable.face_dialog_pulse_light_to_dark
+ }
+ } else {
+ R.drawable.face_dialog_pulse_dark_to_light
+ }
assertThat(iconAsset).isEqualTo(expectedIconAsset)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.biometric_dialog_face_icon_description_authenticating)
assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(true)
}
}
}
@@ -503,9 +532,14 @@
}
if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_error)
assertThat(iconContentDescriptionId).isEqualTo(R.string.keyguard_face_failed)
assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
// Clear error, go to idle
errorJob.join()
@@ -514,6 +548,7 @@
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.biometric_dialog_face_icon_description_idle)
assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
}
if (testCase.isCoex) {
@@ -596,10 +631,15 @@
// If co-ex, using implicit flow (explicit flow always requires confirmation)
if (testCase.isFaceOnly || testCase.isCoex) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
}
}
}
@@ -621,10 +661,15 @@
)
if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_wink_from_dark)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.biometric_dialog_face_icon_description_authenticated)
assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
}
// explicit flow because confirmation requested
@@ -666,10 +711,15 @@
viewModel.confirmAuthenticated()
if (testCase.isFaceOnly) {
+ val shouldRepeatAnimation by collectLastValue(iconViewModel.shouldRepeatAnimation)
+ val shouldPulseAnimation by collectLastValue(iconViewModel.shouldPulseAnimation)
+
+ assertThat(shouldPulseAnimation!!).isEqualTo(false)
assertThat(iconAsset).isEqualTo(R.drawable.face_dialog_dark_to_checkmark)
assertThat(iconContentDescriptionId)
.isEqualTo(R.string.biometric_dialog_face_icon_description_confirmed)
assertThat(shouldAnimateIconView).isEqualTo(true)
+ assertThat(shouldRepeatAnimation).isEqualTo(false)
}
// explicit flow because confirmation requested
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java
index 84b1c00..87dd9b2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsGridLayoutTest.java
@@ -21,11 +21,11 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
-import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -40,7 +40,7 @@
* Tests for {@link ListGridLayout}.
*/
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class GlobalActionsGridLayoutTest extends SysuiTestCase {
private GlobalActionsGridLayout mGridLayout;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java
index 16dcd65..ea0f4d2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsLayoutTest.java
@@ -24,11 +24,11 @@
import static org.mockito.Mockito.spy;
import android.content.Context;
-import android.testing.AndroidTestingRunner;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.MultiListLayout;
@@ -44,7 +44,7 @@
* Tests for {@link ListGridLayout}.
*/
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class GlobalActionsLayoutTest extends SysuiTestCase {
private TestLayout mLayout;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java
index 4c65b90..a10457f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ListGridLayoutTest.java
@@ -18,11 +18,11 @@
import static junit.framework.Assert.assertEquals;
-import android.testing.AndroidTestingRunner;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -36,7 +36,7 @@
* Tests for {@link ListGridLayout}.
*/
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class ListGridLayoutTest extends SysuiTestCase {
private ListGridLayout mListGridLayout;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
index 28c01ad..73509e2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/ShutdownUiTest.java
@@ -26,8 +26,8 @@
import android.os.PowerManager;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.testing.AndroidTestingRunner;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.R;
@@ -41,7 +41,7 @@
import org.mockito.MockitoAnnotations;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class ShutdownUiTest extends SysuiTestCase {
ShutdownUi mShutdownUi;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt
index d7a0d5b5..64cd091 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/domain/interactor/KeyboardBacklightInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyboard.backlight.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
@@ -29,11 +30,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyboardBacklightInteractorTest : SysuiTestCase() {
private val keyboardRepository = FakeKeyboardRepository()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinatorTest.kt
index 7207fbf..47261a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/backlight/ui/KeyboardBacklightDialogCoordinatorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyboard.backlight.ui
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyboard.backlight.domain.interactor.KeyboardBacklightInteractor
@@ -37,7 +38,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
@@ -46,7 +46,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyboardBacklightDialogCoordinatorTest : SysuiTestCase() {
@Mock private lateinit var accessibilityManagerWrapper: AccessibilityManagerWrapper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
index 4410e68..53bcf86 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/data/repository/KeyboardRepositoryTest.kt
@@ -21,6 +21,7 @@
import android.hardware.input.InputManager.KeyboardBacklightListener
import android.hardware.input.KeyboardBacklightState
import android.view.InputDevice
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.FlowValue
@@ -44,7 +45,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
@@ -53,7 +53,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyboardRepositoryTest : SysuiTestCase() {
@Captor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt
index 05a2ca2..0757ea1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/ShortcutHelperActivityStarterTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyboard.shortcut.ui
import android.content.Intent
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyboard.shortcut.fakeShortcutHelperStartActivity
@@ -33,11 +34,10 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class ShortcutHelperActivityStarterTest : SysuiTestCase() {
private val kosmos =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
index 44a8904..34b2aaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/shortcut/ui/viewmodel/ShortcutHelperViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyboard.shortcut.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -34,11 +35,10 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class ShortcutHelperViewModelTest : SysuiTestCase() {
private val kosmos =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
index 59d8fc3..9a721fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/StickyKeysIndicatorCoordinatorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyboard.stickykeys.ui
import android.app.Dialog
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyboard.data.repository.FakeStickyKeysRepository
@@ -36,13 +37,12 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class StickyKeysIndicatorCoordinatorTest : SysuiTestCase() {
private lateinit var coordinator: StickyKeysIndicatorCoordinator
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
index d14d72d..9d9e5be62 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/stickykeys/ui/viewmodel/StickyKeysIndicatorViewModelTest.kt
@@ -19,6 +19,7 @@
import android.hardware.input.InputManager
import android.hardware.input.StickyModifierState
import android.provider.Settings.Secure.ACCESSIBILITY_STICKY_KEYS
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -47,14 +48,13 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentCaptor
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class StickyKeysIndicatorViewModelTest : SysuiTestCase() {
private val dispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
index 5b836b6..925ace2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationRotateTextViewControllerTest.java
@@ -36,9 +36,9 @@
import android.content.res.ColorStateList;
import android.graphics.Color;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.keyguard.logging.KeyguardLogger;
@@ -56,7 +56,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@RunWithLooper
@SmallTest
public class KeyguardIndicationRotateTextViewControllerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
index b44fb8e..325bae1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardIndicationTest.java
@@ -24,10 +24,10 @@
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.drawable.Drawable;
-import android.testing.AndroidTestingRunner;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -35,7 +35,7 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@SmallTest
public class KeyguardIndicationTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index ce8028c..909acca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -34,7 +34,6 @@
import android.net.Uri;
import android.os.Handler;
import android.provider.Settings;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -44,6 +43,7 @@
import androidx.slice.SliceSpecs;
import androidx.slice.builders.ListBuilder;
import androidx.slice.core.SliceQuery;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
@@ -72,7 +72,7 @@
import java.util.concurrent.TimeUnit;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@RunWithLooper
public class KeyguardSliceProviderTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 6ebda4d..128dd23 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -7,7 +7,6 @@
import android.graphics.Rect
import android.os.PowerManager
import android.platform.test.annotations.DisableFlags
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import android.view.RemoteAnimationTarget
import android.view.SurfaceControl
@@ -15,6 +14,7 @@
import android.view.View
import android.view.ViewRootImpl
import android.view.WindowManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
import com.android.systemui.Flags
@@ -46,7 +46,7 @@
import org.mockito.MockitoAnnotations
import java.util.function.Predicate
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@RunWithLooper
@SmallTest
class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
index 977116e..144e634 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ResourceTrimmerTest.kt
@@ -1,20 +1,15 @@
package com.android.systemui.keyguard
import android.content.ComponentCallbacks2
-import android.graphics.HardwareRenderer
-import android.platform.test.annotations.DisableFlags
-import android.platform.test.annotations.EnableFlags
import android.platform.test.flag.junit.SetFlagsRule
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.DisableSceneContainer
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
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testDispatcher
@@ -26,7 +21,6 @@
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.any
import com.android.systemui.utils.GlobalWindowManager
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runCurrent
@@ -43,7 +37,7 @@
import org.mockito.MockitoAnnotations
@OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class ResourceTrimmerTest : SysuiTestCase() {
val kosmos = testKosmos()
@@ -62,52 +56,21 @@
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, true)
keyguardRepository.setDozeAmount(0f)
keyguardRepository.setKeyguardGoingAway(false)
-
- val withDeps =
- KeyguardInteractorFactory.create(
- featureFlags = featureFlags,
- repository = keyguardRepository,
- )
- val keyguardInteractor = withDeps.keyguardInteractor
resourceTrimmer =
ResourceTrimmer(
- keyguardInteractor,
- powerInteractor = powerInteractor,
keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
globalWindowManager = globalWindowManager,
applicationScope = testScope.backgroundScope,
bgDispatcher = kosmos.testDispatcher,
- featureFlags = featureFlags,
sceneInteractor = kosmos.sceneInteractor,
)
resourceTrimmer.start()
}
@Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- fun noChange_noOutputChanges() =
- testScope.runTest {
- testScope.runCurrent()
- verifyZeroInteractions(globalWindowManager)
- }
-
- @Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- fun dozeAodDisabled_sleep_trimsMemory() =
- testScope.runTest {
- powerInteractor.setAsleepForTest()
- testScope.runCurrent()
- verify(globalWindowManager, times(1))
- .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
- }
-
- @Test
- @DisableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- fun dozeAodDisabled_flagDisabled_sleep_doesntTrimMemory() =
+ fun dozeAodDisabled_sleep_doesntTrimMemory() =
testScope.runTest {
powerInteractor.setAsleepForTest()
testScope.runCurrent()
@@ -115,8 +78,7 @@
}
@Test
- @DisableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- fun dozeEnabled_flagDisabled_sleepWithFullDozeAmount_doesntTrimMemory() =
+ fun dozeEnabled_sleepWithFullDozeAmount_doesntTrimMemory() =
testScope.runTest {
keyguardRepository.setDreaming(true)
keyguardRepository.setDozeAmount(1f)
@@ -126,20 +88,6 @@
}
@Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- fun dozeEnabled_sleepWithFullDozeAmount_trimsMemory() =
- testScope.runTest {
- keyguardRepository.setDreaming(true)
- keyguardRepository.setDozeAmount(1f)
- powerInteractor.setAsleepForTest()
- testScope.runCurrent()
- verify(globalWindowManager, times(1))
- .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
- }
-
- @Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
fun dozeEnabled_sleepWithoutFullDozeAmount_doesntTrimMemory() =
testScope.runTest {
keyguardRepository.setDreaming(true)
@@ -150,34 +98,6 @@
}
@Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- fun aodEnabled_sleepWithFullDozeAmount_trimsMemoryOnce() {
- testScope.runTest {
- keyguardRepository.setDreaming(true)
- keyguardRepository.setDozeAmount(0f)
- powerInteractor.setAsleepForTest()
-
- testScope.runCurrent()
- verifyZeroInteractions(globalWindowManager)
-
- generateSequence(0f) { it + 0.1f }
- .takeWhile { it < 1f }
- .forEach {
- keyguardRepository.setDozeAmount(it)
- testScope.runCurrent()
- }
- verifyZeroInteractions(globalWindowManager)
-
- keyguardRepository.setDozeAmount(1f)
- testScope.runCurrent()
- verify(globalWindowManager, times(1))
- .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_ALL)
- }
- }
-
- @Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
fun aodEnabled_deviceWakesHalfWayThrough_doesNotTrimMemory() {
testScope.runTest {
keyguardRepository.setDreaming(true)
@@ -209,7 +129,6 @@
}
@Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
@DisableSceneContainer
fun keyguardTransitionsToGone_trimsFontCache() =
testScope.runTest {
@@ -220,12 +139,10 @@
)
verify(globalWindowManager, times(1))
.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
verifyNoMoreInteractions(globalWindowManager)
}
@Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
@EnableSceneContainer
fun keyguardTransitionsToGone_trimsFontCache_scene_container() =
testScope.runTest {
@@ -233,38 +150,6 @@
verify(globalWindowManager, times(1))
.trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(1)).trimCaches(HardwareRenderer.CACHE_TRIM_FONT)
verifyNoMoreInteractions(globalWindowManager)
}
-
- @Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- @DisableSceneContainer
- fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache() =
- testScope.runTest {
- featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
- keyguardTransitionRepository.sendTransitionSteps(
- from = KeyguardState.LOCKSCREEN,
- to = KeyguardState.GONE,
- testScope
- )
- // Memory hidden should still be called.
- verify(globalWindowManager, times(1))
- .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(0)).trimCaches(any())
- }
-
- @Test
- @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK)
- @EnableSceneContainer
- fun keyguardTransitionsToGone_flagDisabled_doesNotTrimFontCache_scene_container() =
- testScope.runTest {
- featureFlags.set(Flags.TRIM_FONT_CACHES_AT_UNLOCK, false)
- kosmos.setSceneTransition(Idle(Scenes.Gone))
-
- // Memory hidden should still be called.
- verify(globalWindowManager, times(1))
- .trimMemory(ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN)
- verify(globalWindowManager, times(0)).trimCaches(any())
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java
index 70a0415..99ff2d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ScreenLifecycleTest.java
@@ -21,8 +21,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
-import android.testing.AndroidTestingRunner;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -35,7 +35,7 @@
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@SmallTest
public class ScreenLifecycleTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
index 39a453d..a9f7d00 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WakefulnessLifecycleTest.java
@@ -24,8 +24,8 @@
import android.app.IWallpaperManager;
import android.os.PowerManager;
-import android.testing.AndroidTestingRunner;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -39,7 +39,7 @@
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@SmallTest
public class WakefulnessLifecycleTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
index c7f1dec..d435a47 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
@@ -25,8 +25,8 @@
import android.os.Looper
import android.os.UserHandle
import android.os.UserManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import androidx.test.runner.AndroidJUnit4
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.util.mockito.any
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt
index bd4525b..47bf653 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfigTest.kt
@@ -18,16 +18,16 @@
import android.content.Intent
import android.net.Uri
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
index df1833e..5fa194d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt
@@ -203,4 +203,4 @@
verify(ringerModeInternal).removeObserver(any())
coroutineContext.cancelChildren()
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
index f5b5261..972ca02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepositoryTest.kt
@@ -19,6 +19,7 @@
package com.android.systemui.keyguard.data.repository
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.common.ui.data.repository.ConfigurationRepository
@@ -37,11 +38,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class KeyguardBlueprintRepositoryTest : SysuiTestCase() {
private lateinit var underTest: KeyguardBlueprintRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
index 9aac8e2..af5187d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardClockRepositoryTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.repository
import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.ClockEventController
import com.android.systemui.SysuiTestCase
@@ -36,11 +37,10 @@
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class KeyguardClockRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
index a320845..8b8a6cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardSmartspaceRepositoryImplTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.repository
import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -31,10 +32,9 @@
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.MockitoAnnotations
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class KeyguardSmartspaceRepositoryImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 8be1e7a..a8271c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.domain.interactor
import android.os.Handler
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardSecurityModel
import com.android.keyguard.KeyguardUpdateMonitor
@@ -56,7 +57,6 @@
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.junit.MockitoJUnit
@@ -64,7 +64,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
@JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 14d9548..d2a9c58 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -63,11 +63,11 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import platform.test.runner.parameterized.Parameter
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.junit.runners.Parameterized.Parameter
-import org.junit.runners.Parameterized.Parameters
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
@@ -83,7 +83,7 @@
detail = "on certain architectures all permutations with startActivity=true is causing failures"
)
@SmallTest
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@DisableSceneContainer
class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
index ced3526..9d06031 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorSceneContainerTest.kt
@@ -63,11 +63,11 @@
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import org.junit.Before
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import platform.test.runner.parameterized.Parameter
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.junit.runners.Parameterized.Parameter
-import org.junit.runners.Parameterized.Parameters
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.ArgumentMatchers.eq
@@ -83,7 +83,7 @@
detail = "on certain architectures all permutations with startActivity=true is causing failures"
)
@SmallTest
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@EnableSceneContainer
class KeyguardQuickAffordanceInteractorSceneContainerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
index 8a0613f..baa8ed7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.view.layout
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
@@ -28,7 +29,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
@@ -36,7 +36,7 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class KeyguardBlueprintCommandListenerTest : SysuiTestCase() {
private lateinit var keyguardBlueprintCommandListener: KeyguardBlueprintCommandListener
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
index 344e0fc..9fab0d90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/blueprints/DefaultKeyguardBlueprintTest.kt
@@ -17,10 +17,10 @@
package com.android.systemui.keyguard.ui.view.layout.blueprints
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.communal.ui.view.layout.sections.CommunalTutorialIndicatorSection
@@ -54,7 +54,7 @@
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@RunWithLooper(setAsMainLooper = true)
@ExperimentalCoroutinesApi
@SmallTest
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 b3cc5c9..4a39a9b 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
@@ -22,6 +22,7 @@
import android.view.View.GONE
import android.view.View.VISIBLE
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -52,12 +53,11 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.MockitoAnnotations
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class ClockSectionTest : SysuiTestCase() {
private lateinit var underTest: ClockSection
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
index 4f2b690..693a877 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt
@@ -21,6 +21,7 @@
import android.view.WindowManager
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.LegacyLockIconViewController
import com.android.systemui.Flags as AConfigFlags
@@ -44,14 +45,13 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Answers
import org.mockito.Mock
import org.mockito.Mockito.mock
import org.mockito.MockitoAnnotations
@ExperimentalCoroutinesApi
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class DefaultDeviceEntrySectionTest : SysuiTestCase() {
@Mock private lateinit var authController: AuthController
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
index 711f90f..10f7128 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
@@ -20,6 +20,7 @@
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags as AConfigFlags
import com.android.systemui.SysuiTestCase
@@ -30,11 +31,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class DefaultIndicationAreaSectionTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index be10b82..7d4f034 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -22,6 +22,7 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.constraintlayout.widget.ConstraintSet.GONE
import androidx.constraintlayout.widget.ConstraintSet.VISIBLE
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
@@ -41,11 +42,10 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class SmartspaceSectionTest : SysuiTestCase() {
private lateinit var underTest: SmartspaceSection
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
index f1c93c4..e44bc7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -35,11 +36,10 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mockito.verify
@ExperimentalCoroutinesApi
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class AlternateBouncerViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
index 391831a..6398a5a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.keyguardUpdateMonitor
import com.android.systemui.Flags
@@ -37,11 +38,10 @@
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.kotlin.whenever
@ExperimentalCoroutinesApi
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class AlternateBouncerWindowViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
index ec2a1d3..129752e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBlueprintViewModelTest.kt
@@ -18,6 +18,7 @@
package com.android.systemui.keyguard.ui.viewmodel
import android.os.fakeExecutorHandler
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
@@ -25,12 +26,11 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class KeyguardBlueprintViewModelTest : SysuiTestCase() {
@Mock private lateinit var keyguardBlueprintInteractor: KeyguardBlueprintInteractor
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 8a12a90..16dd9af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -20,6 +20,7 @@
import android.app.admin.DevicePolicyManager
import android.content.Intent
import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.LockPatternUtils
import com.android.systemui.Flags as AConfigFlags
@@ -49,6 +50,7 @@
import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.plugins.ActivityStarter
@@ -75,7 +77,6 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.ArgumentMatchers
import org.mockito.Mock
import org.mockito.Mockito
@@ -83,7 +84,7 @@
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() {
@Mock private lateinit var activityStarter: ActivityStarter
@@ -289,6 +290,7 @@
underTest =
KeyguardQuickAffordancesCombinedViewModel(
+ applicationScope = kosmos.applicationCoroutineScope,
quickAffordanceInteractor =
KeyguardQuickAffordanceInteractor(
keyguardInteractor = keyguardInteractor,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
index 78f4dcc..0c3fcb3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModelTest.kt
@@ -16,6 +16,7 @@
package com.android.systemui.keyguard.ui.viewmodel
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -32,13 +33,12 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
import org.mockito.Answers
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class KeyguardSmartspaceViewModelTest : SysuiTestCase() {
val kosmos = testKosmos()
val testScope = kosmos.testScope
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
index 447b333..fbeb6d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/SessionTrackerTest.java
@@ -32,9 +32,9 @@
import static org.mockito.Mockito.when;
import android.os.RemoteException;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceId;
@@ -54,7 +54,7 @@
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
@SmallTest
public class SessionTrackerTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt
index ab19b3a..d2e6dad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/log/core/LoggerTest.kt
@@ -1,5 +1,6 @@
package com.android.systemui.log.core
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.LogMessageImpl
@@ -16,10 +17,9 @@
import org.mockito.Mockito.isNull
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
-import org.mockito.junit.MockitoJUnitRunner
@SmallTest
-@RunWith(MockitoJUnitRunner::class)
+@RunWith(AndroidJUnit4::class)
class LoggerTest : SysuiTestCase() {
@Mock private lateinit var buffer: MessageBuffer
private lateinit var message: LogMessage
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
index 5986f4a..e2a2b7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataFilterImplTest.kt
@@ -18,8 +18,8 @@
import android.app.smartspace.SmartspaceAction
import android.os.Bundle
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
@@ -64,7 +64,7 @@
private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!!
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class LegacyMediaDataFilterImplTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
index dd05a0d..544350c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataCombineLatestTest.java
@@ -26,9 +26,9 @@
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceId;
@@ -48,7 +48,7 @@
import java.util.ArrayList;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper
public class MediaDataCombineLatestTest extends SysuiTestCase {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
index caaa42f..8471fe1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataFilterImplTest.kt
@@ -18,8 +18,8 @@
import android.app.smartspace.SmartspaceAction
import android.os.Bundle
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.systemui.SysuiTestCase
@@ -76,7 +76,7 @@
@ExperimentalCoroutinesApi
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class MediaDataFilterImplTest : SysuiTestCase() {
val kosmos = testKosmos()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
index 3bf4173..3906c40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDataProcessorTest.kt
@@ -191,7 +191,7 @@
@Before
fun setup() {
- whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+ whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
staticMockSession =
ExtendedMockito.mockitoSession()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
index 16d8819..42bd46f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaDeviceManagerTest.kt
@@ -31,8 +31,8 @@
import android.platform.test.annotations.EnableFlags
import android.platform.test.annotations.RequiresFlagsDisabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
import com.android.settingslib.bluetooth.LocalBluetoothManager
@@ -86,7 +86,7 @@
private const val NORMAL_APP_NAME = "NORMAL_APP_NAME"
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
public class MediaDeviceManagerTest : SysuiTestCase() {
@get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
index 31a2435..efe4413 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaSessionBasedFilterTest.kt
@@ -20,8 +20,8 @@
import android.media.session.MediaController.PlaybackInfo
import android.media.session.MediaSession
import android.media.session.MediaSessionManager
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.MediaTestUtils
@@ -53,7 +53,7 @@
MediaTestUtils.emptyMediaData.copy(packageName = PACKAGE, notificationKey = NOTIF_KEY)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
public class MediaSessionBasedFilterTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
index 6ca0bef..c1bba4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/MediaTimeoutListenerTest.kt
@@ -20,7 +20,7 @@
import android.media.session.MediaController
import android.media.session.MediaSession
import android.media.session.PlaybackState
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.MediaTestUtils
@@ -65,7 +65,7 @@
}
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class MediaTimeoutListenerTest : SysuiTestCase() {
@Mock private lateinit var mediaControllerFactory: MediaControllerFactory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt
index 55ff231..02d7413 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/MediaResumeListenerTest.kt
@@ -27,8 +27,8 @@
import android.media.MediaDescription
import android.media.session.MediaSession
import android.provider.Settings
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
@@ -76,7 +76,7 @@
private fun <T> any(): T = Mockito.any<T>()
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class MediaResumeListenerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt
index 8dfa5b8..dca19690 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/resume/ResumeMediaBrowserTest.kt
@@ -23,8 +23,8 @@
import android.media.session.MediaController
import android.media.session.MediaSession
import android.service.media.MediaBrowserService
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.mock
@@ -53,7 +53,7 @@
private fun <T> any(): T = Mockito.any<T>()
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
public class ResumeMediaBrowserTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt
index b509e77..addf008 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaPlayerDataTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.media.controls.ui
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.MediaTestUtils
@@ -34,7 +34,7 @@
import org.mockito.junit.MockitoJUnit
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
public class MediaPlayerDataTest : SysuiTestCase() {
@Mock private lateinit var playerIsPlaying: MediaControlPanel
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
index 4fcd3bb..cdcb143 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/AnimationBindHandlerTest.kt
@@ -18,8 +18,8 @@
import android.graphics.drawable.Animatable2
import android.graphics.drawable.Drawable
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.assertFalse
@@ -37,7 +37,7 @@
import org.mockito.junit.MockitoJUnit
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class AnimationBindHandlerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt
index aa297b5..4d0605f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/ColorSchemeTransitionTest.kt
@@ -18,8 +18,8 @@
import android.animation.ValueAnimator
import android.graphics.Color
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.ui.view.GutsViewHolder
@@ -44,7 +44,7 @@
private const val TARGET_COLOR = Color.BLUE
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class ColorSchemeTransitionTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
index bb95ba3..2499c9c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/animation/MetadataAnimationHandlerTest.kt
@@ -17,8 +17,8 @@
package com.android.systemui.media.controls.ui.animation
import android.animation.Animator
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.fail
@@ -36,7 +36,7 @@
import org.mockito.junit.MockitoJUnit
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class MetadataAnimationHandlerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
index 8a6b272..4e14fec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/binder/SeekBarObserverTest.kt
@@ -18,11 +18,11 @@
import android.animation.Animator
import android.animation.ObjectAnimator
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import android.widget.SeekBar
import android.widget.TextView
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.ui.drawable.SquigglyProgress
@@ -40,7 +40,7 @@
import org.mockito.junit.MockitoJUnit
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class SeekBarObserverTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
index 791563a..2f95936 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/KeyguardMediaControllerTest.kt
@@ -17,11 +17,11 @@
package com.android.systemui.media.controls.ui.controller
import android.provider.Settings
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View.GONE
import android.view.View.VISIBLE
import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
@@ -50,7 +50,7 @@
import org.mockito.junit.MockitoJUnit
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class KeyguardMediaControllerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
index a89139b..3ca5b42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaCarouselControllerTest.kt
@@ -22,10 +22,10 @@
import android.database.ContentObserver
import android.os.LocaleList
import android.provider.Settings
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.util.MathUtils.abs
import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.keyguard.KeyguardUpdateMonitor
@@ -40,6 +40,7 @@
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.kosmos.testDispatcher
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.MediaTestUtils
@@ -107,7 +108,7 @@
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class MediaCarouselControllerTest : SysuiTestCase() {
val kosmos = testKosmos()
@@ -158,6 +159,7 @@
testDispatcher = UnconfinedTestDispatcher()
mediaCarouselController =
MediaCarouselController(
+ applicationScope = kosmos.applicationCoroutineScope,
context = context,
mediaControlPanelFactory = mediaControlPanelFactory,
visualStabilityProvider = visualStabilityProvider,
@@ -893,7 +895,10 @@
mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
mediaCarouselController.mediaCarousel = mediaCarousel
- val settingsJob = mediaCarouselController.listenForLockscreenSettingChanges(this)
+ val settingsJob =
+ mediaCarouselController.listenForLockscreenSettingChanges(
+ kosmos.applicationCoroutineScope
+ )
secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, false)
val keyguardJob = mediaCarouselController.listenForAnyStateToLockscreenTransition(this)
@@ -920,7 +925,10 @@
mediaCarouselController.updateHostVisibility = { updatedVisibility = true }
mediaCarouselController.mediaCarousel = mediaCarousel
- val settingsJob = mediaCarouselController.listenForLockscreenSettingChanges(this)
+ val settingsJob =
+ mediaCarouselController.listenForLockscreenSettingChanges(
+ kosmos.applicationCoroutineScope
+ )
secureSettings.putBool(Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, true)
val keyguardJob = mediaCarouselController.listenForAnyStateToLockscreenTransition(this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
index 6d7976e..ecc456c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaControlPanelTest.kt
@@ -45,7 +45,6 @@
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.provider.Settings
import android.provider.Settings.ACTION_MEDIA_CONTROLS_SETTINGS
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.util.TypedValue
import android.view.View
@@ -60,6 +59,7 @@
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.LiveData
import androidx.media.utils.MediaConstants
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.internal.widget.CachingIconView
@@ -137,7 +137,7 @@
private const val APP_NAME = "APP_NAME"
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class MediaControlPanelTest : SysuiTestCase() {
@get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
index 5f7c386..bba01bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaHierarchyManagerTest.kt
@@ -18,10 +18,10 @@
import android.graphics.Rect
import android.provider.Settings
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.ViewGroup
import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.keyguard.KeyguardViewController
import com.android.systemui.SysuiTestCase
@@ -29,7 +29,9 @@
import com.android.systemui.controls.controller.ControlsControllerImplTest.Companion.eq
import com.android.systemui.dreams.DreamOverlayStateController
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
import com.android.systemui.keyguard.shared.model.KeyguardState
import com.android.systemui.kosmos.testScope
import com.android.systemui.media.controls.domain.pipeline.MediaDataManager
@@ -49,7 +51,6 @@
import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
import com.android.systemui.testKosmos
import com.android.systemui.util.animation.UniqueObjectHostView
-import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.settings.FakeSettings
@@ -75,10 +76,12 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
class MediaHierarchyManagerTest : SysuiTestCase() {
@@ -112,12 +115,14 @@
private val testScope = kosmos.testScope
private lateinit var mediaHierarchyManager: MediaHierarchyManager
private lateinit var isQsBypassingShade: MutableStateFlow<Boolean>
+ private lateinit var shadeExpansion: MutableStateFlow<Float>
private lateinit var mediaFrame: ViewGroup
private val configurationController = FakeConfigurationController()
private val settings = FakeSettings()
private lateinit var testableLooper: TestableLooper
private lateinit var fakeHandler: FakeHandler
private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+ private val keyguardRepository = kosmos.fakeKeyguardRepository
@Before
fun setup() {
@@ -129,7 +134,9 @@
fakeHandler = FakeHandler(testableLooper.looper)
whenever(mediaCarouselController.mediaFrame).thenReturn(mediaFrame)
isQsBypassingShade = MutableStateFlow(false)
+ shadeExpansion = MutableStateFlow(0f)
whenever(shadeInteractor.isQsBypassingShade).thenReturn(isQsBypassingShade)
+ whenever(shadeInteractor.shadeExpansion).thenReturn(shadeExpansion)
whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(false)
mediaHierarchyManager =
MediaHierarchyManager(
@@ -141,6 +148,7 @@
mediaDataManager,
keyguardViewController,
dreamOverlayStateController,
+ kosmos.keyguardInteractor,
kosmos.communalTransitionViewModel,
configurationController,
wakefulnessLifecycle,
@@ -191,7 +199,7 @@
verify(mediaCarouselController)
.onDesiredLocationChanged(
ArgumentMatchers.anyInt(),
- any(MediaHostState::class.java),
+ any<MediaHostState>(),
anyBoolean(),
anyLong(),
anyLong()
@@ -204,7 +212,7 @@
verify(mediaCarouselController, times(0))
.onDesiredLocationChanged(
ArgumentMatchers.anyInt(),
- any(MediaHostState::class.java),
+ any<MediaHostState>(),
anyBoolean(),
anyLong(),
anyLong()
@@ -218,7 +226,7 @@
verify(mediaCarouselController)
.onDesiredLocationChanged(
ArgumentMatchers.anyInt(),
- any(MediaHostState::class.java),
+ any<MediaHostState>(),
anyBoolean(),
anyLong(),
anyLong()
@@ -231,7 +239,7 @@
verify(mediaCarouselController, times(0))
.onDesiredLocationChanged(
ArgumentMatchers.anyInt(),
- any(MediaHostState::class.java),
+ any<MediaHostState>(),
anyBoolean(),
anyLong(),
anyLong()
@@ -245,7 +253,7 @@
verify(mediaCarouselController)
.onDesiredLocationChanged(
ArgumentMatchers.anyInt(),
- any(MediaHostState::class.java),
+ any<MediaHostState>(),
anyBoolean(),
anyLong(),
anyLong()
@@ -255,7 +263,7 @@
verify(mediaCarouselController)
.onDesiredLocationChanged(
ArgumentMatchers.anyInt(),
- any(MediaHostState::class.java),
+ any<MediaHostState>(),
anyBoolean(),
anyLong(),
anyLong()
@@ -269,7 +277,7 @@
verify(mediaCarouselController)
.onDesiredLocationChanged(
ArgumentMatchers.anyInt(),
- any(MediaHostState::class.java),
+ any<MediaHostState>(),
anyBoolean(),
anyLong(),
anyLong()
@@ -281,7 +289,7 @@
verify(mediaCarouselController)
.onDesiredLocationChanged(
ArgumentMatchers.anyInt(),
- any(MediaHostState::class.java),
+ any<MediaHostState>(),
anyBoolean(),
anyLong(),
anyLong()
@@ -297,7 +305,7 @@
verify(mediaCarouselController)
.onDesiredLocationChanged(
eq(MediaHierarchyManager.LOCATION_QQS),
- any(MediaHostState::class.java),
+ any<MediaHostState>(),
eq(false),
anyLong(),
anyLong()
@@ -309,7 +317,7 @@
verify(mediaCarouselController)
.onDesiredLocationChanged(
eq(MediaHierarchyManager.LOCATION_LOCKSCREEN),
- any(MediaHostState::class.java),
+ any<MediaHostState>(),
eq(false),
anyLong(),
anyLong()
@@ -482,6 +490,26 @@
}
@Test
+ fun isCurrentlyInGuidedTransformation_desiredLocationIsHub_returnsFalse() =
+ testScope.runTest {
+ goToLockscreen()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.LOCKSCREEN,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ mediaHierarchyManager.qsExpansion = 0f
+ mediaHierarchyManager.setTransitionToFullShadeAmount(123f)
+
+ whenever(lockHost.visible).thenReturn(true)
+ whenever(qsHost.visible).thenReturn(true)
+ whenever(qqsHost.visible).thenReturn(true)
+ whenever(hubModeHost.visible).thenReturn(true)
+
+ assertThat(mediaHierarchyManager.isCurrentlyInGuidedTransformation()).isFalse()
+ }
+
+ @Test
fun testDream() {
goToDream()
setMediaDreamComplicationEnabled(true)
@@ -499,7 +527,7 @@
verify(mediaCarouselController)
.onDesiredLocationChanged(
eq(MediaHierarchyManager.LOCATION_QQS),
- any(MediaHostState::class.java),
+ any<MediaHostState>(),
eq(false),
anyLong(),
anyLong()
@@ -532,7 +560,7 @@
verify(mediaCarouselController)
.onDesiredLocationChanged(
eq(MediaHierarchyManager.LOCATION_QQS),
- any(MediaHostState::class.java),
+ any<MediaHostState>(),
eq(false),
anyLong(),
anyLong()
@@ -590,7 +618,50 @@
verify(mediaCarouselController)
.onDesiredLocationChanged(
eq(MediaHierarchyManager.LOCATION_QS),
- any(MediaHostState::class.java),
+ any<MediaHostState>(),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ }
+
+ @Test
+ fun testCommunalLocation_whenDreamingAndShadeExpanding() =
+ testScope.runTest {
+ keyguardRepository.setDreaming(true)
+ runCurrent()
+ keyguardTransitionRepository.sendTransitionSteps(
+ from = KeyguardState.DREAMING,
+ to = KeyguardState.GLANCEABLE_HUB,
+ testScope = testScope,
+ )
+ // Mock the behavior for dreaming that pulling down shade will immediately set QS as
+ // expanded
+ expandQS()
+ // Starts opening the shade
+ shadeExpansion.value = 0.1f
+ runCurrent()
+
+ // UMO shows on hub
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_COMMUNAL_HUB),
+ anyOrNull(),
+ eq(false),
+ anyLong(),
+ anyLong()
+ )
+ clearInvocations(mediaCarouselController)
+
+ // The shade is opened enough to make QS elements visible
+ shadeExpansion.value = 0.5f
+ runCurrent()
+
+ // UMO shows on QS
+ verify(mediaCarouselController)
+ .onDesiredLocationChanged(
+ eq(MediaHierarchyManager.LOCATION_QS),
+ any<MediaHostState>(),
eq(false),
anyLong(),
anyLong()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
index e5d3082..00b9a46 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/controller/MediaViewControllerTest.kt
@@ -23,7 +23,6 @@
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.RippleDrawable
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import android.view.ViewGroup
@@ -35,6 +34,7 @@
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintSet
import androidx.lifecycle.LiveData
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.widget.CachingIconView
import com.android.systemui.SysuiTestCase
@@ -72,7 +72,7 @@
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class MediaViewControllerTest : SysuiTestCase() {
private val mediaHostStateHolder = MediaHost.MediaHostStateHolder()
private val mediaHostStatesManager = MediaHostStatesManager()
@@ -376,7 +376,7 @@
@Test
fun attachPlayer_seekBarDisabled_seekBarVisibilityIsSetToInvisible() {
- whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+ whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
mediaViewController.attachPlayer(viewHolder)
getEnabledChangeListener().onEnabledChanged(enabled = true)
@@ -388,7 +388,7 @@
@Test
fun attachPlayer_seekBarEnabled_seekBarVisible() {
- whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+ whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
mediaViewController.attachPlayer(viewHolder)
getEnabledChangeListener().onEnabledChanged(enabled = true)
@@ -399,7 +399,7 @@
@Test
fun attachPlayer_seekBarStatusUpdate_seekBarVisibilityChanges() {
- whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+ whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
mediaViewController.attachPlayer(viewHolder)
getEnabledChangeListener().onEnabledChanged(enabled = true)
@@ -415,7 +415,7 @@
@Test
fun attachPlayer_notScrubbing_scrubbingViewsGone() {
- whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+ whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
mediaViewController.attachPlayer(viewHolder)
mediaViewController.canShowScrubbingTime = true
@@ -435,7 +435,7 @@
@Test
fun setIsScrubbing_noSemanticActions_scrubbingViewsGone() {
- whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+ whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
mediaViewController.attachPlayer(viewHolder)
mediaViewController.canShowScrubbingTime = false
@@ -454,7 +454,7 @@
@Test
fun setIsScrubbing_noPrevButton_scrubbingTimesNotShown() {
- whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+ whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
mediaViewController.attachPlayer(viewHolder)
mediaViewController.setUpNextButtonInfo(true)
@@ -476,7 +476,7 @@
@Test
fun setIsScrubbing_noNextButton_scrubbingTimesNotShown() {
- whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+ whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
mediaViewController.attachPlayer(viewHolder)
mediaViewController.setUpNextButtonInfo(false)
@@ -498,7 +498,7 @@
@Test
fun setIsScrubbing_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() {
- whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+ whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
mediaViewController.attachPlayer(viewHolder)
mediaViewController.setUpNextButtonInfo(true)
@@ -524,7 +524,7 @@
@Test
fun setIsScrubbing_trueThenFalse_reservePrevAndNextButtons() {
- whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true)
+ whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true)
mediaViewController.attachPlayer(viewHolder)
mediaViewController.setUpNextButtonInfo(true, ConstraintSet.INVISIBLE)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt
index 0319aaa..e87f176 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/drawable/SquigglyProgressTest.kt
@@ -21,8 +21,8 @@
import android.graphics.LightingColorFilter
import android.graphics.Paint
import android.graphics.Rect
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.graphics.ColorUtils
import com.android.systemui.SysuiTestCase
@@ -40,7 +40,7 @@
import org.mockito.junit.MockitoJUnit
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class SquigglyProgressTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
index 1208369..d073cf1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaCarouselScrollHandlerTest.kt
@@ -16,9 +16,9 @@
package com.android.systemui.media.controls.ui.view
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.MotionEvent
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.media.controls.util.MediaUiEventLogger
@@ -38,7 +38,7 @@
@SmallTest
@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class MediaCarouselScrollHandlerTest : SysuiTestCase() {
private val carouselWidth = 1038
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt
index d3c703c..cdb060c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/view/MediaViewHolderTest.kt
@@ -16,17 +16,17 @@
package com.android.systemui.media.controls.ui.view
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.LayoutInflater
import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import org.junit.Test
import org.junit.runner.RunWith
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class MediaViewHolderTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
index e1c2d3f..4da7b2a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/viewmodel/SeekBarViewModelTest.kt
@@ -20,12 +20,12 @@
import android.media.session.MediaController
import android.media.session.MediaSession
import android.media.session.PlaybackState
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.MotionEvent
import android.widget.SeekBar
import androidx.arch.core.executor.ArchTaskExecutor
import androidx.arch.core.executor.TaskExecutor
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.Classifier
@@ -53,7 +53,7 @@
import org.mockito.junit.MockitoJUnit
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class SeekBarViewModelTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt
index 86f3062..bb9d20f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/util/MediaDataUtilsTest.kt
@@ -16,8 +16,8 @@
package com.android.systemui.media.controls.util
-import android.testing.AndroidTestingRunner
import android.util.Pair as APair
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
@@ -25,7 +25,7 @@
import org.junit.runner.RunWith
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class MediaDataUtilsTest : SysuiTestCase() {
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 45ae506..856bc0b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -59,12 +59,12 @@
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
-import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.text.TextUtils;
import android.view.View;
import androidx.core.graphics.drawable.IconCompat;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
@@ -95,7 +95,7 @@
import java.util.List;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class MediaOutputControllerTest extends SysuiTestCase {
private static final String TEST_DEVICE_1_ID = "test_device_1_id";
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
index 1e8fbea..50ae25c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogReceiverTest.java
@@ -27,8 +27,8 @@
import android.content.Intent;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
-import android.testing.AndroidTestingRunner;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.settingslib.flags.Flags;
@@ -40,7 +40,7 @@
import org.junit.runner.RunWith;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class MediaOutputDialogReceiverTest extends SysuiTestCase {
private MediaOutputDialogReceiver mMediaOutputDialogReceiver;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
index a828843..8e9a1f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaComplicationViewControllerTest.java
@@ -19,9 +19,9 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
-import android.testing.AndroidTestingRunner;
import android.widget.FrameLayout;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -35,7 +35,7 @@
import org.mockito.MockitoAnnotations;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class MediaComplicationViewControllerTest extends SysuiTestCase {
@Mock
private MediaHost mMediaHost;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
index 8f8630e..a49819e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dream/MediaDreamSentinelTest.java
@@ -25,8 +25,8 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.testing.AndroidTestingRunner;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -44,7 +44,7 @@
import org.mockito.MockitoAnnotations;
@SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
public class MediaDreamSentinelTest extends SysuiTestCase {
@Mock
MediaDataManager mMediaDataManager;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 27f59d29..f1d833f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -23,13 +23,13 @@
import android.media.MediaRoute2Info
import android.os.Handler
import android.os.PowerManager
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.InstanceId
import com.android.internal.logging.testing.UiEventLoggerFake
@@ -59,7 +59,7 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class MediaTttChipControllerReceiverTest : SysuiTestCase() {
private lateinit var controllerReceiver: MediaTttChipControllerReceiver
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index 3be50b1..111b8d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -24,7 +24,6 @@
import android.os.PowerManager
import android.os.VibrationAttributes
import android.os.VibrationEffect
-import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.view.View
import android.view.ViewGroup
@@ -32,6 +31,7 @@
import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import android.widget.TextView
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.statusbar.IUndoMediaTransferCallback
@@ -74,7 +74,7 @@
import org.mockito.MockitoAnnotations
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@TestableLooper.RunWithLooper
class MediaTttSenderCoordinatorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
index da448aa..6238257 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLoggerTest.kt
@@ -1,7 +1,7 @@
package com.android.systemui.mediaprojection
import android.media.projection.IMediaProjectionManager
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP
import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST
@@ -13,7 +13,7 @@
import org.junit.runner.RunWith
import org.mockito.Mockito.verify
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class MediaProjectionMetricsLoggerTest : SysuiTestCase() {
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 2536078..22bdfe8 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
@@ -2,7 +2,7 @@
import android.content.ComponentName
import android.os.UserHandle
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
@@ -24,7 +24,7 @@
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class MediaProjectionAppSelectorControllerTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
index db36131..a73df07 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ActivityTaskManagerThumbnailLoaderTest.kt
@@ -7,9 +7,9 @@
import android.graphics.Point
import android.graphics.Rect
import android.hardware.HardwareBuffer
-import android.testing.AndroidTestingRunner
import android.view.Surface
import android.window.TaskSnapshot
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.recents.model.ThumbnailData
@@ -24,7 +24,7 @@
import org.junit.Test
import org.junit.runner.RunWith
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
@OptIn(ExperimentalCoroutinesApi::class)
class ActivityTaskManagerThumbnailLoaderTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
index fa1c8f8..a0cd835 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/BasicPackageManagerAppIconLoaderTest.kt
@@ -20,6 +20,7 @@
import android.content.pm.ActivityInfo
import android.content.pm.PackageManager
import android.graphics.Bitmap
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.icons.FastBitmapDrawable
import com.android.systemui.SysuiTestCase
@@ -31,10 +32,9 @@
import kotlinx.coroutines.runBlocking
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class BasicPackageManagerAppIconLoaderTest : SysuiTestCase() {
private val packageManagerWrapper: PackageManagerWrapper = mock()
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 6ac86f5..2f61579 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
@@ -3,7 +3,7 @@
import android.app.ActivityManager.RecentTaskInfo
import android.content.pm.UserInfo
import android.os.UserManager
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.mediaprojection.appselector.data.RecentTask.UserType.CLONED
@@ -25,7 +25,7 @@
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class ShellRecentTaskListProviderTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
index e487780..b7fefc0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/data/repository/MediaProjectionManagerRepositoryTest.kt
@@ -17,8 +17,8 @@
package com.android.systemui.mediaprojection.data.repository
import android.os.Binder
-import android.testing.AndroidTestingRunner
import android.view.ContentRecordingSession
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -34,7 +34,7 @@
import org.junit.Test
import org.junit.runner.RunWith
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class MediaProjectionManagerRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
index c63efa1..ea5603d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolverTest.kt
@@ -25,10 +25,11 @@
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Before
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
+import platform.test.runner.parameterized.Parameter
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.junit.runners.Parameterized.Parameters
import org.mockito.ArgumentMatchers.any
abstract class BaseScreenCaptureDevicePolicyResolverTest(private val precondition: Preconditions) :
@@ -81,7 +82,7 @@
}
}
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
class IsAllowedScreenCaptureDevicePolicyResolverTest(
private val test: IsScreenCaptureAllowedTestCase
@@ -468,7 +469,7 @@
}
}
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
@SmallTest
class IsCompletelyNotAllowedScreenCaptureDevicePolicyResolverTest(
private val test: IsScreenCaptureCompletelyDisabledTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
index 16c92ec..8fe8878 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/MediaProjectionTaskSwitcherCoreStartableTest.kt
@@ -16,7 +16,7 @@
package com.android.systemui.mediaprojection.taskswitcher
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags.FLAG_PSS_TASK_SWITCHER
import com.android.systemui.SysuiTestCase
@@ -27,7 +27,7 @@
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class MediaProjectionTaskSwitcherCoreStartableTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
index bda0e1e..d3ce871 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/data/repository/ActivityTaskManagerTasksRepositoryTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.mediaprojection.taskswitcher.data.repository
import android.os.Binder
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -32,7 +32,7 @@
import org.junit.Test
import org.junit.runner.RunWith
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class ActivityTaskManagerTasksRepositoryTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
index 33e65f26..7fe55b8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/domain/interactor/TaskSwitchInteractorTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.mediaprojection.taskswitcher.domain.interactor
import android.content.Intent
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -35,7 +35,7 @@
import org.junit.Test
import org.junit.runner.RunWith
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class TaskSwitchInteractorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
index ad18099..7417dac 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/TaskSwitcherNotificationCoordinatorTest.kt
@@ -18,7 +18,7 @@
import android.app.Notification
import android.app.NotificationManager
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.kosmos.testScope
@@ -43,7 +43,7 @@
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class TaskSwitcherNotificationCoordinatorTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
index a468953..5bedc13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/taskswitcher/ui/viewmodel/TaskSwitcherNotificationViewModelTest.kt
@@ -17,7 +17,7 @@
package com.android.systemui.mediaprojection.taskswitcher.ui.viewmodel
import android.content.Intent
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -37,7 +37,7 @@
import org.junit.Test
import org.junit.runner.RunWith
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
@SmallTest
class TaskSwitcherNotificationViewModelTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
index c06a28e..a3be9e3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateExtTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.model
import android.view.Display
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.settings.FakeDisplayTracker
@@ -24,10 +25,9 @@
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
@SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
class SysUiStateExtTest : SysuiTestCase() {
private val kosmos = testKosmos()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java
index f03f4f7..9a78bd9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/model/SysUiStateTest.java
@@ -23,6 +23,7 @@
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
@@ -32,10 +33,9 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.mockito.junit.MockitoJUnitRunner;
@SmallTest
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(AndroidJUnit4.class)
public class SysUiStateTest extends SysuiTestCase {
private static final int FLAG_1 = 1;
private static final int FLAG_2 = 1 << 1;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
index f1c97dd..23cf7fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt
@@ -55,6 +55,7 @@
companion object {
private const val START_X: Float = 0f
}
+
private val kosmos = testKosmos()
private lateinit var mBackPanelController: BackPanelController
private lateinit var systemClock: FakeSystemClock
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index 6956bea..09d6a1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -39,7 +39,6 @@
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
@@ -51,6 +50,7 @@
import android.view.ViewGroup;
import android.widget.FrameLayout;
+import androidx.compose.ui.platform.ComposeView;
import androidx.lifecycle.Lifecycle;
import androidx.test.filters.SmallTest;
@@ -58,12 +58,10 @@
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.EnableSceneContainer;
-import com.android.systemui.flags.FeatureFlagsClassic;
import com.android.systemui.media.controls.ui.view.MediaHost;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSComponent;
import com.android.systemui.qs.external.TileServiceRequestController;
-import com.android.systemui.qs.footer.ui.binder.FooterActionsViewBinder;
import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel;
import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.res.R;
@@ -112,9 +110,7 @@
@Mock private QSSquishinessController mSquishinessController;
@Mock private FooterActionsViewModel mFooterActionsViewModel;
@Mock private FooterActionsViewModel.Factory mFooterActionsViewModelFactory;
- @Mock private FooterActionsViewBinder mFooterActionsViewBinder;
@Mock private LargeScreenShadeInterpolator mLargeScreenShadeInterpolator;
- @Mock private FeatureFlagsClassic mFeatureFlags;
private ViewGroup mQsView;
private final CommandQueue mCommandQueue =
@@ -259,6 +255,39 @@
}
@Test
+ public void setQsExpansion_whenShouldUpdateSquishinessTrue_setsSquishinessBasedOnFraction() {
+ enableSplitShade();
+ when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
+ float expansion = 0.456f;
+ float panelExpansionFraction = 0.678f;
+ float proposedTranslation = 567f;
+ float squishinessFraction = 0.789f;
+
+ mUnderTest.setShouldUpdateSquishinessOnMedia(true);
+ mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+ squishinessFraction);
+
+ verify(mQSMediaHost).setSquishFraction(squishinessFraction);
+ }
+
+ @Test
+ public void setQsExpansion_whenOnKeyguardAndShouldUpdateSquishinessFalse_setsSquishiness() {
+ // Random test values without any meaning. They just have to be different from each other.
+ float expansion = 0.123f;
+ float panelExpansionFraction = 0.321f;
+ float proposedTranslation = 456f;
+ float squishinessFraction = 0.567f;
+
+ enableSplitShade();
+ setStatusBarCurrentAndUpcomingState(KEYGUARD);
+ mUnderTest.setShouldUpdateSquishinessOnMedia(false);
+ mUnderTest.setQsExpansion(expansion, panelExpansionFraction, proposedTranslation,
+ squishinessFraction);
+
+ verify(mQSMediaHost).setSquishFraction(1.0f);
+ }
+
+ @Test
public void setQsExpansion_inSplitShade_setsFooterActionsExpansion_basedOnPanelExpFraction() {
// Random test values without any meaning. They just have to be different from each other.
float expansion = 0.123f;
@@ -496,18 +525,13 @@
@Test
@EnableSceneContainer
public void testSceneContainerFlagsEnabled_FooterActionsRemoved_controllerNotStarted() {
- clearInvocations(
- mFooterActionsViewBinder, mFooterActionsViewModel, mFooterActionsViewModelFactory);
+ clearInvocations(mFooterActionsViewModel, mFooterActionsViewModelFactory);
QSImpl other = instantiate();
other.onComponentCreated(mQsComponent, null);
assertThat((View) other.getView().findViewById(R.id.qs_footer_actions)).isNull();
- verifyZeroInteractions(
- mFooterActionsViewModel,
- mFooterActionsViewBinder,
- mFooterActionsViewModelFactory
- );
+ verifyZeroInteractions(mFooterActionsViewModel, mFooterActionsViewModelFactory);
}
@Test
@@ -553,9 +577,7 @@
mock(QSLogger.class),
mock(FooterActionsController.class),
mFooterActionsViewModelFactory,
- mFooterActionsViewBinder,
- mLargeScreenShadeInterpolator,
- mFeatureFlags
+ mLargeScreenShadeInterpolator
);
}
@@ -589,41 +611,20 @@
customizer.setId(android.R.id.edit);
mQsView.addView(customizer);
- View footerActionsView = new FooterActionsViewBinder().create(mContext);
+ ComposeView footerActionsView = new ComposeView(mContext);
footerActionsView.setId(R.id.qs_footer_actions);
mQsView.addView(footerActionsView);
}
private void setUpInflater() {
- LayoutInflater realInflater = LayoutInflater.from(mContext);
-
when(mLayoutInflater.cloneInContext(any(Context.class))).thenReturn(mLayoutInflater);
when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class), anyBoolean()))
- .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0),
- (ViewGroup) invocation.getArgument(1),
- (boolean) invocation.getArgument(2)));
+ .thenAnswer((invocation) -> mQsView);
when(mLayoutInflater.inflate(anyInt(), nullable(ViewGroup.class)))
- .thenAnswer((invocation) -> inflate(realInflater, (int) invocation.getArgument(0),
- (ViewGroup) invocation.getArgument(1)));
+ .thenAnswer((invocation) -> mQsView);
mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, mLayoutInflater);
}
- private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root) {
- return inflate(realInflater, layoutRes, root, root != null);
- }
-
- private View inflate(LayoutInflater realInflater, int layoutRes, @Nullable ViewGroup root,
- boolean attachToRoot) {
- if (layoutRes == R.layout.footer_actions
- || layoutRes == R.layout.footer_actions_text_button
- || layoutRes == R.layout.footer_actions_number_button
- || layoutRes == R.layout.footer_actions_icon_button) {
- return realInflater.inflate(layoutRes, root, attachToRoot);
- }
-
- return mQsView;
- }
-
private void setupQsComponent() {
when(mQsComponent.getQSPanelController()).thenReturn(mQSPanelController);
when(mQsComponent.getQuickQSPanelController()).thenReturn(mQuickQSPanelController);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
index 2da4b72..87031d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/GridConsistencyInteractorTest.kt
@@ -31,9 +31,6 @@
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
@@ -46,22 +43,21 @@
class GridConsistencyInteractorTest : SysuiTestCase() {
private val iconOnlyTiles =
- MutableStateFlow(
- setOf(
- TileSpec.create("smallA"),
- TileSpec.create("smallB"),
- TileSpec.create("smallC"),
- TileSpec.create("smallD"),
- TileSpec.create("smallE"),
- )
+ setOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
)
private val kosmos =
testKosmos().apply {
iconTilesRepository =
object : IconTilesRepository {
- override val iconTilesSpecs: StateFlow<Set<TileSpec>>
- get() = iconOnlyTiles.asStateFlow()
+ override fun isIconTile(spec: TileSpec): Boolean {
+ return iconOnlyTiles.contains(spec)
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
index bda48ad..1eb6d63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/panels/domain/interactor/InfiniteGridConsistencyInteractorTest.kt
@@ -25,9 +25,6 @@
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.runner.RunWith
@@ -37,21 +34,20 @@
class InfiniteGridConsistencyInteractorTest : SysuiTestCase() {
private val iconOnlyTiles =
- MutableStateFlow(
- setOf(
- TileSpec.create("smallA"),
- TileSpec.create("smallB"),
- TileSpec.create("smallC"),
- TileSpec.create("smallD"),
- TileSpec.create("smallE"),
- )
+ setOf(
+ TileSpec.create("smallA"),
+ TileSpec.create("smallB"),
+ TileSpec.create("smallC"),
+ TileSpec.create("smallD"),
+ TileSpec.create("smallE"),
)
private val kosmos =
testKosmos().apply {
iconTilesRepository =
object : IconTilesRepository {
- override val iconTilesSpecs: StateFlow<Set<TileSpec>>
- get() = iconOnlyTiles.asStateFlow()
+ override fun isIconTile(spec: TileSpec): Boolean {
+ return iconOnlyTiles.contains(spec)
+ }
}
}
private val underTest = with(kosmos) { infiniteGridConsistencyInteractor }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
index db11c3e..196f654 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt
@@ -19,6 +19,8 @@
import android.content.Context
import android.graphics.Rect
import android.graphics.drawable.Drawable
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.service.quicksettings.Tile
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -28,11 +30,13 @@
import android.view.accessibility.AccessibilityNodeInfo
import android.widget.TextView
import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS
import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.haptics.qs.QSLongPressEffect
import com.android.systemui.haptics.qs.qsLongPressEffect
import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.qsTileFactory
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -536,10 +540,30 @@
assertThat(tileView.haveLongPressPropertiesBeenReset).isTrue()
}
+ @Test
+ @EnableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS)
+ fun onInit_withLongPressEffect_longPressEffectHasTileAndExpandable() {
+ val tile = kosmos.qsTileFactory.createTile("Test Tile")
+ tileView.init(tile)
+
+ assertThat(tileView.isTileAddedToLongPress).isTrue()
+ assertThat(tileView.isExpandableAddedToLongPress).isTrue()
+ }
+
+ @Test
+ @DisableFlags(FLAG_QUICK_SETTINGS_VISUAL_HAPTICS_LONGPRESS)
+ fun onInit_withoutLongPressEffect_longPressEffectDoesNotHaveTileAndExpandable() {
+ val tile = kosmos.qsTileFactory.createTile("Test Tile")
+ tileView.init(tile)
+
+ assertThat(tileView.isTileAddedToLongPress).isFalse()
+ assertThat(tileView.isExpandableAddedToLongPress).isFalse()
+ }
+
class FakeTileView(
context: Context,
collapsed: Boolean,
- longPressEffect: QSLongPressEffect?,
+ private val longPressEffect: QSLongPressEffect?,
) : QSTileViewImpl(
ContextThemeWrapper(context, R.style.Theme_SystemUI_QuickSettings),
collapsed,
@@ -547,6 +571,11 @@
) {
var constantLongPressEffectDuration = 500
+ val isTileAddedToLongPress: Boolean
+ get() = longPressEffect?.qsTile != null
+ val isExpandableAddedToLongPress: Boolean
+ get() = longPressEffect?.expandable != null
+
override fun getLongPressEffectDuration(): Int = constantLongPressEffectDuration
fun changeState(state: QSTile.State) {
handleStateChanged(state)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
index bde1445..b8267a0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt
@@ -18,6 +18,8 @@
import android.graphics.Rect
import android.os.PowerManager
+import android.platform.test.annotations.DisableFlags
+import android.platform.test.annotations.EnableFlags
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import android.testing.ViewUtils
@@ -30,6 +32,7 @@
import com.android.compose.animation.scene.ObservableTransitionState
import com.android.compose.animation.scene.SceneKey
import com.android.systemui.Flags
+import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE
import com.android.systemui.SysuiTestCase
import com.android.systemui.ambient.touch.TouchHandler
import com.android.systemui.ambient.touch.TouchMonitor
@@ -51,6 +54,7 @@
import com.android.systemui.res.R
import com.android.systemui.scene.shared.model.sceneDataSourceDelegator
import com.android.systemui.shade.domain.interactor.shadeInteractor
+import com.android.systemui.statusbar.notification.stack.notificationStackScrollLayoutController
import com.android.systemui.testKosmos
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
@@ -64,9 +68,11 @@
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyFloat
import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@ExperimentalCoroutinesApi
@@ -124,6 +130,7 @@
ambientTouchComponentFactory,
communalContent,
kosmos.sceneDataSourceDelegator,
+ kosmos.notificationStackScrollLayoutController
)
}
testableLooper = TestableLooper.get(this)
@@ -166,6 +173,7 @@
ambientTouchComponentFactory,
communalContent,
kosmos.sceneDataSourceDelegator,
+ kosmos.notificationStackScrollLayoutController
)
// First call succeeds.
@@ -176,6 +184,7 @@
}
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
@Test
fun onTouchEvent_communalClosed_doesNotIntercept() =
with(kosmos) {
@@ -187,6 +196,7 @@
}
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
@Test
fun onTouchEvent_openGesture_interceptsTouches() =
with(kosmos) {
@@ -204,6 +214,7 @@
}
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
@Test
fun onTouchEvent_communalTransitioning_interceptsTouches() =
with(kosmos) {
@@ -230,6 +241,7 @@
}
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
@Test
fun onTouchEvent_communalOpen_interceptsTouches() =
with(kosmos) {
@@ -244,6 +256,7 @@
}
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
@Test
fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() =
with(kosmos) {
@@ -262,6 +275,7 @@
}
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
@Test
fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() =
with(kosmos) {
@@ -278,6 +292,7 @@
}
}
+ @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
@Test
fun onTouchEvent_containerViewDisposed_doesNotIntercept() =
with(kosmos) {
@@ -310,6 +325,7 @@
ambientTouchComponentFactory,
communalContent,
kosmos.sceneDataSourceDelegator,
+ kosmos.notificationStackScrollLayoutController,
)
assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
@@ -329,6 +345,7 @@
ambientTouchComponentFactory,
communalContent,
kosmos.sceneDataSourceDelegator,
+ kosmos.notificationStackScrollLayoutController,
)
// Only initView without attaching a view as we don't want the flows to start collecting
@@ -499,13 +516,30 @@
}
}
+ @Test
+ @EnableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE)
+ fun fullScreenSwipeGesture_doNotProcessTouchesInNotificationStack() =
+ with(kosmos) {
+ testScope.runTest {
+ // Communal is closed.
+ goToScene(CommunalScenes.Blank)
+ `when`(
+ notificationStackScrollLayoutController.isBelowLastNotification(
+ anyFloat(),
+ anyFloat()
+ )
+ )
+ .thenReturn(false)
+ assertThat(underTest.onTouchEvent(DOWN_EVENT)).isFalse()
+ }
+ }
+
private fun initAndAttachContainerView() {
containerView = View(context)
parentView = FrameLayout(context)
- parentView.addView(containerView)
- underTest.initView(containerView)
+ parentView.addView(underTest.initView(containerView))
// Attach the view so that flows start collecting.
ViewUtils.attachView(parentView, CONTAINER_WIDTH, CONTAINER_HEIGHT)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 041adea..c3cedf8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -837,6 +837,7 @@
mJavaAdapter,
mCastController,
new ResourcesSplitShadeStateController(),
+ () -> mKosmos.getCommunalTransitionViewModel(),
() -> mLargeScreenHeaderHelper
);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
index 845744a..85541aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerImplBaseTest.java
@@ -308,6 +308,7 @@
new JavaAdapter(mTestScope.getBackgroundScope()),
mCastController,
splitShadeStateController,
+ () -> mKosmos.getCommunalTransitionViewModel(),
() -> mLargeScreenHeaderHelper
);
mQsController.init();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
index 5363c57..308b370 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/carrier/ShadeCarrierGroupControllerTest.java
@@ -312,9 +312,10 @@
info = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{""},
- true,
+ /* anySimReady= */ true,
+ /* isInSatelliteMode= */ false,
new int[]{0},
- true /* airplaneMode */);
+ /* airplaneMode= */ true);
mCallback.updateCarrierInfo(info);
mTestableLooper.processAllMessages();
assertEquals(View.GONE, mShadeCarrierGroup.getNoSimTextView().getVisibility());
@@ -326,15 +327,59 @@
info = new CarrierTextManager.CarrierTextCallbackInfo(
"",
new CharSequence[]{FIRST_CARRIER_NAME, ""},
- true,
+ /* anySimReady= */ true,
+ /* isInSatelliteMode= */ false,
new int[]{0, 1},
- false /* airplaneMode */);
+ /* airplaneMode= */ false);
mCallback.updateCarrierInfo(info);
mTestableLooper.processAllMessages();
assertEquals(View.VISIBLE, mShadeCarrierGroupController.getShadeCarrierVisibility(0));
}
@Test
+ public void isInSatelliteMode_true_noSimViewShownWithText() {
+ CarrierTextManager.CarrierTextCallbackInfo
+ info = new CarrierTextManager.CarrierTextCallbackInfo(
+ "Satellite Mode Test",
+ new CharSequence[]{FIRST_CARRIER_NAME},
+ /* anySimReady= */ true,
+ /* isInSatelliteMode= */ true,
+ new int[]{1},
+ /* airplaneMode= */ false);
+
+ mCallback.updateCarrierInfo(info);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mShadeCarrierGroup.getNoSimTextView().getVisibility()).isEqualTo(View.VISIBLE);
+ assertThat(mShadeCarrierGroup.getNoSimTextView().getText()).isEqualTo(
+ "Satellite Mode Test");
+
+ verify(mShadeCarrier1).setVisibility(View.GONE);
+ verify(mShadeCarrier2).setVisibility(View.GONE);
+ verify(mShadeCarrier3).setVisibility(View.GONE);
+ }
+
+ @Test
+ public void isInSatelliteMode_false_normalSimViewsShown() {
+ CarrierTextManager.CarrierTextCallbackInfo
+ info = new CarrierTextManager.CarrierTextCallbackInfo(
+ "Satellite Mode Test",
+ new CharSequence[]{FIRST_CARRIER_NAME, SECOND_CARRIER_NAME},
+ /* anySimReady= */ true,
+ /* isInSatelliteMode= */ false,
+ new int[]{0, 1},
+ /* airplaneMode= */ false);
+
+ mCallback.updateCarrierInfo(info);
+ mTestableLooper.processAllMessages();
+
+ assertThat(mShadeCarrierGroup.getNoSimTextView().getVisibility()).isEqualTo(View.GONE);
+
+ verify(mShadeCarrier1).setVisibility(View.VISIBLE);
+ verify(mShadeCarrier2).setVisibility(View.VISIBLE);
+ }
+
+ @Test
public void testListenerNotCalledOnRegistreation() {
mShadeCarrierGroupController
.setOnSingleCarrierChangedListener(mOnSingleCarrierChangedListener);
@@ -350,8 +395,7 @@
SINGLE_CARRIER_TEXT,
new CharSequence[]{SINGLE_CARRIER_TEXT},
true,
- new int[]{0},
- false /* airplaneMode */);
+ new int[]{0});
mCallback.updateCarrierInfo(info);
mTestableLooper.processAllMessages();
@@ -369,8 +413,7 @@
MULTI_CARRIER_TEXT,
new CharSequence[]{FIRST_CARRIER_NAME, SECOND_CARRIER_NAME},
true,
- new int[]{0, 1},
- false /* airplaneMode */);
+ new int[]{0, 1});
mCallback.updateCarrierInfo(info);
mTestableLooper.processAllMessages();
@@ -387,16 +430,14 @@
SINGLE_CARRIER_TEXT,
new CharSequence[]{FIRST_CARRIER_NAME},
true,
- new int[]{0},
- false /* airplaneMode */);
+ new int[]{0});
CarrierTextManager.CarrierTextCallbackInfo
multiCarrierInfo = new CarrierTextManager.CarrierTextCallbackInfo(
MULTI_CARRIER_TEXT,
new CharSequence[]{FIRST_CARRIER_NAME, SECOND_CARRIER_NAME},
true,
- new int[]{0, 1},
- false /* airplaneMode */);
+ new int[]{0, 1});
mCallback.updateCarrierInfo(singleCarrierInfo);
mTestableLooper.processAllMessages();
@@ -421,8 +462,7 @@
SINGLE_CARRIER_TEXT,
new CharSequence[]{FIRST_CARRIER_NAME},
true,
- new int[]{0},
- false /* airplaneMode */);
+ new int[]{0});
mCallback.updateCarrierInfo(singleCarrierInfo);
mTestableLooper.processAllMessages();
@@ -443,8 +483,7 @@
MULTI_CARRIER_TEXT,
new CharSequence[]{FIRST_CARRIER_NAME, SECOND_CARRIER_NAME},
true,
- new int[]{0, 1},
- false /* airplaneMode */);
+ new int[]{0, 1});
mCallback.updateCarrierInfo(multiCarrierInfo);
mTestableLooper.processAllMessages();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index 63ce233..068e166 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -32,6 +32,7 @@
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.provider.Settings
+import android.testing.TestableLooper.RunWithLooper
import android.view.View
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
@@ -84,6 +85,7 @@
import java.util.concurrent.Executor
@SmallTest
+@RunWithLooper(setAsMainLooper = true)
class LockscreenSmartspaceControllerTest : SysuiTestCase() {
companion object {
const val SMARTSPACE_TIME_TOO_EARLY = 1000L
@@ -778,6 +780,7 @@
}
@Test
+ @RunWithLooper(setAsMainLooper = false)
fun testConnectAttemptBeforeInitializationShouldNotCreateSession() {
// GIVEN an uninitalized smartspaceView
// WHEN the device is provisioned
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
index 7903a73..e984200 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt
@@ -91,7 +91,8 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+ AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ uiEventLogger)
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -110,7 +111,8 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+ AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ uiEventLogger)
) {
ensurePeekState()
assertShouldNotHeadsUp(
@@ -129,7 +131,8 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+ AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ uiEventLogger)
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -146,7 +149,8 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+ AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ uiEventLogger)
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -163,7 +167,8 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+ AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ uiEventLogger)
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -180,7 +185,8 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+ AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ uiEventLogger)
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -197,7 +203,8 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+ AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ uiEventLogger)
) {
assertFsiNotSuppressed()
}
@@ -208,7 +215,8 @@
avalancheProvider.startTime = whenAgo(10)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+ AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ uiEventLogger)
) {
ensurePeekState()
assertShouldHeadsUp(
@@ -232,7 +240,8 @@
).thenReturn(PERMISSION_GRANTED)
withFilter(
- AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager)
+ AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager,
+ uiEventLogger)
) {
ensurePeekState()
assertShouldHeadsUp(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index a355cd1..62bffdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -140,7 +140,7 @@
mSmartReplyStateInflater,
mNotifLayoutInflaterFactoryProvider,
mHeadsUpStyleProvider,
- mock(NotificationContentInflaterLogger.class));
+ mock(NotificationRowContentBinderLogger.class));
}
@Test
@@ -265,7 +265,7 @@
R.layout.custom_view_dark);
}
},
- mock(NotificationContentInflaterLogger.class));
+ mock(NotificationRowContentBinderLogger.class));
assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 1661860..65941ad 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -192,7 +192,7 @@
mBgCoroutineContext,
mMainCoroutineContext);
- NotificationContentInflater contentBinder = new NotificationContentInflater(
+ NotificationRowContentBinder contentBinder = new NotificationContentInflater(
mock(NotifRemoteViewCache.class),
mock(NotificationRemoteInputManager.class),
mock(ConversationNotificationProcessor.class),
@@ -201,7 +201,7 @@
new MockSmartReplyInflater(),
mock(NotifLayoutInflaterFactory.Provider.class),
mock(HeadsUpStyleProvider.class),
- mock(NotificationContentInflaterLogger.class));
+ mock(NotificationRowContentBinderLogger.class));
contentBinder.setInflateSynchronously(true);
mBindStage = new RowContentBindStage(contentBinder,
mock(NotifInflationErrorManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
index e025d3d..d366632 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/SingleLineViewInflaterTest.kt
@@ -372,7 +372,7 @@
}
// Inflate the SingleLineViewModel
- // Mock the behavior of NotificationContentInflater.doInBackground
+ // Mock the behavior of NotificationRowContentBinder.doInBackground
val messagingStyle = builder.getMessagingStyle()
val isConversation = type is OneToOneConversation || type is GroupConversation
return SingleLineViewInflater.inflateSingleLineViewModel(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
index eb2538e..7d586cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileRepositorySwitcherTest.kt
@@ -131,8 +131,9 @@
mobileMappings,
fakeBroadcastDispatcher,
context,
- IMMEDIATE,
+ /* bgDispatcher = */ IMMEDIATE,
scope,
+ /* mainDispatcher = */ IMMEDIATE,
FakeAirplaneModeRepository(),
wifiRepository,
mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
index 96e599f..76982ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionsRepositoryTest.kt
@@ -141,8 +141,8 @@
private val wifiPickerTrackerCallback =
argumentCaptor<WifiPickerTracker.WifiPickerTrackerCallback>()
- private val dispatcher = StandardTestDispatcher()
- private val testScope = TestScope(dispatcher)
+ private val testDispatcher = StandardTestDispatcher()
+ private val testScope = TestScope(testDispatcher)
private lateinit var underTest: MobileConnectionsRepositoryImpl
@@ -194,7 +194,7 @@
flags,
testScope.backgroundScope,
mainExecutor,
- dispatcher,
+ testDispatcher,
wifiPickerTrackerFactory,
wifiManager,
wifiLogBuffer,
@@ -216,7 +216,7 @@
fakeBroadcastDispatcher,
connectivityManager,
telephonyManager = telephonyManager,
- bgDispatcher = dispatcher,
+ bgDispatcher = testDispatcher,
logger = logger,
mobileMappingsProxy = mobileMappings,
scope = testScope.backgroundScope,
@@ -249,8 +249,9 @@
mobileMappings,
fakeBroadcastDispatcher,
context,
- dispatcher,
+ /* bgDispatcher = */ testDispatcher,
testScope.backgroundScope,
+ /* mainDispatcher = */ testDispatcher,
airplaneModeRepository,
wifiRepository,
fullConnectionFactory,
@@ -1225,8 +1226,9 @@
mobileMappings,
fakeBroadcastDispatcher,
context,
- dispatcher,
+ testDispatcher,
testScope.backgroundScope,
+ testDispatcher,
airplaneModeRepository,
wifiRepository,
fullConnectionFactory,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
index 02f53b6..d24d87c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/satellite/data/prod/DeviceBasedSatelliteRepositoryImplTest.kt
@@ -34,6 +34,7 @@
import android.telephony.satellite.SatelliteManager.SATELLITE_MODEM_STATE_UNKNOWN
import android.telephony.satellite.SatelliteManager.SatelliteException
import android.telephony.satellite.SatelliteModemStateCallback
+import android.telephony.satellite.SatelliteSupportedStateCallback
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -327,7 +328,6 @@
@Test
fun satelliteNotSupported_listenersAreNotRegistered() =
testScope.runTest {
- setupDefaultRepo()
// GIVEN satellite is not supported
setUpRepo(
uptime = MIN_UPTIME,
@@ -345,6 +345,110 @@
}
@Test
+ fun satelliteSupported_registersCallbackForStateChanges() =
+ testScope.runTest {
+ // GIVEN a supported satellite manager.
+ setupDefaultRepo()
+ runCurrent()
+
+ // THEN the repo registers for state changes of satellite support
+ verify(satelliteManager, times(1)).registerForSupportedStateChanged(any(), any())
+ }
+
+ @Test
+ fun satelliteNotSupported_registersCallbackForStateChanges() =
+ testScope.runTest {
+ // GIVEN satellite is not supported
+ setUpRepo(
+ uptime = MIN_UPTIME,
+ satMan = satelliteManager,
+ satelliteSupported = false,
+ )
+
+ runCurrent()
+ // THEN the repo registers for state changes of satellite support
+ verify(satelliteManager, times(1)).registerForSupportedStateChanged(any(), any())
+ }
+
+ @Test
+ fun satelliteSupportedStateChangedCallbackThrows_doesNotCrash() =
+ testScope.runTest {
+ // GIVEN, satellite manager throws when registering for supported state changes
+ whenever(satelliteManager.registerForSupportedStateChanged(any(), any()))
+ .thenThrow(IllegalStateException())
+
+ // GIVEN a supported satellite manager.
+ setupDefaultRepo()
+ runCurrent()
+
+ // THEN a listener for satellite supported changed can attempt to register,
+ // with no crash
+ verify(satelliteManager).registerForSupportedStateChanged(any(), any())
+ }
+
+ @Test
+ fun satelliteSupported_supportIsLost_unregistersListeners() =
+ testScope.runTest {
+ // GIVEN a supported satellite manager.
+ setupDefaultRepo()
+ runCurrent()
+
+ val callback =
+ withArgCaptor<SatelliteSupportedStateCallback> {
+ verify(satelliteManager).registerForSupportedStateChanged(any(), capture())
+ }
+
+ // WHEN data is requested from the repo
+ val connectionState by collectLastValue(underTest.connectionState)
+ val signalStrength by collectLastValue(underTest.signalStrength)
+
+ // THEN the listeners are registered
+ verify(satelliteManager, times(1)).registerForModemStateChanged(any(), any())
+ verify(satelliteManager, times(1)).registerForNtnSignalStrengthChanged(any(), any())
+
+ // WHEN satellite support turns off
+ callback.onSatelliteSupportedStateChanged(false)
+ runCurrent()
+
+ // THEN listeners are unregistered
+ verify(satelliteManager, times(1)).unregisterForModemStateChanged(any())
+ verify(satelliteManager, times(1)).unregisterForNtnSignalStrengthChanged(any())
+ }
+
+ @Test
+ fun satelliteNotSupported_supportShowsUp_registersListeners() =
+ testScope.runTest {
+ // GIVEN satellite is not supported
+ setUpRepo(
+ uptime = MIN_UPTIME,
+ satMan = satelliteManager,
+ satelliteSupported = false,
+ )
+ runCurrent()
+
+ val callback =
+ withArgCaptor<SatelliteSupportedStateCallback> {
+ verify(satelliteManager).registerForSupportedStateChanged(any(), capture())
+ }
+
+ // WHEN data is requested from the repo
+ val connectionState by collectLastValue(underTest.connectionState)
+ val signalStrength by collectLastValue(underTest.signalStrength)
+
+ // THEN the listeners are not yet registered
+ verify(satelliteManager, times(0)).registerForModemStateChanged(any(), any())
+ verify(satelliteManager, times(0)).registerForNtnSignalStrengthChanged(any(), any())
+
+ // WHEN satellite support turns on
+ callback.onSatelliteSupportedStateChanged(true)
+ runCurrent()
+
+ // THEN listeners are registered
+ verify(satelliteManager, times(1)).registerForModemStateChanged(any(), any())
+ verify(satelliteManager, times(1)).registerForNtnSignalStrengthChanged(any(), any())
+ }
+
+ @Test
fun repoDoesNotCheckForSupportUntilMinUptime() =
testScope.runTest {
// GIVEN we init 100ms after sysui starts up
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt
index a05b5e6..ad5242e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/data/repository/FakeScreenBrightnessRepository.kt
@@ -19,7 +19,7 @@
import android.hardware.display.BrightnessInfo
import android.hardware.display.BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE
import android.hardware.display.BrightnessInfo.HIGH_BRIGHTNESS_MODE_OFF
-import com.android.systemui.brightness.data.model.LinearBrightness
+import com.android.systemui.brightness.shared.model.LinearBrightness
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt
index 22784e4..0e84273 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/domain/interactor/ScreenBrightnessInteractorKosmos.kt
@@ -18,6 +18,15 @@
import com.android.systemui.brightness.data.repository.screenBrightnessRepository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.log.table.TableLogBuffer
+import com.android.systemui.util.mockito.mock
val Kosmos.screenBrightnessInteractor by
- Kosmos.Fixture { ScreenBrightnessInteractor(screenBrightnessRepository) }
+ Kosmos.Fixture {
+ ScreenBrightnessInteractor(
+ screenBrightnessRepository,
+ applicationCoroutineScope,
+ mock<TableLogBuffer>(),
+ )
+ }
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 b862078..0b28e3f 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
@@ -29,6 +29,7 @@
import com.android.systemui.common.ui.domain.interactor.configurationInteractor
import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository
import com.android.systemui.communal.domain.interactor.communalInteractor
+import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel
import com.android.systemui.concurrency.fakeExecutor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
@@ -87,6 +88,7 @@
val configurationInteractor by lazy { kosmos.configurationInteractor }
val bouncerRepository by lazy { kosmos.bouncerRepository }
val communalRepository by lazy { kosmos.fakeCommunalSceneRepository }
+ val communalTransitionViewModel by lazy { kosmos.communalTransitionViewModel }
val headsUpNotificationInteractor by lazy { kosmos.headsUpNotificationInteractor }
val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
val keyguardBouncerRepository by lazy { kosmos.fakeKeyguardBouncerRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
index 604c16f..5ff44e5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
@@ -17,6 +17,8 @@
package com.android.systemui.qs.pipeline.data.repository
import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.retail.data.repository.FakeRetailModeRepository
+import com.android.systemui.retail.data.repository.RetailModeRepository
/** This fake uses 0 as the minimum number of tiles. That means that no tiles is a valid state. */
var Kosmos.fakeMinimumTilesRepository by Kosmos.Fixture { MinimumTilesFixedRepository(0) }
@@ -46,3 +48,6 @@
val Kosmos.fakeCustomTileAddedRepository by Kosmos.Fixture { FakeCustomTileAddedRepository() }
var Kosmos.customTileAddedRepository: CustomTileAddedRepository by
Kosmos.Fixture { fakeCustomTileAddedRepository }
+
+val Kosmos.fakeRetailModeRepository by Kosmos.Fixture { FakeRetailModeRepository() }
+var Kosmos.retailModeRepository: RetailModeRepository by Kosmos.Fixture { fakeRetailModeRepository }
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 b870039..d97a5b2 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
@@ -24,6 +24,7 @@
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
+import com.android.systemui.qs.pipeline.data.repository.retailModeRepository
import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
import com.android.systemui.qs.pipeline.shared.logging.qsLogger
import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository
@@ -39,6 +40,7 @@
installedTilesRepository,
userRepository,
minimumTilesRepository,
+ retailModeRepository,
customTileStatePersister,
{ newQSTileFactory },
qsTileFactory,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
index 297d1d8..0a3a2ee 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeLockscreenInteractorKosmos.kt
@@ -17,7 +17,7 @@
package com.android.systemui.shade.domain.interactor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.applicationCoroutineScope
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.shade.data.repository.shadeRepository
import com.android.systemui.util.mockito.mock
@@ -25,7 +25,8 @@
val Kosmos.shadeLockscreenInteractor by
Kosmos.Fixture {
ShadeLockscreenInteractorImpl(
- scope = testScope,
+ applicationScope = applicationCoroutineScope,
+ backgroundScope = applicationCoroutineScope,
shadeInteractor = shadeInteractorImpl,
sceneInteractor = sceneInteractor,
lockIconViewController = mock(),
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt
index 0854e93..569429f 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProviderImpl.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt
@@ -13,14 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.dreams.homecontrols
-import android.app.Activity
-import android.service.dreams.DreamService
-import javax.inject.Inject
+package com.android.systemui.statusbar.notification.stack
-class DreamActivityProviderImpl @Inject constructor() : DreamActivityProvider {
- override fun getActivity(dreamService: DreamService): Activity {
- return dreamService.activity
- }
-}
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+val Kosmos.notificationStackScrollLayoutController by
+ Kosmos.Fixture { mock<NotificationStackScrollLayoutController>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt
index 3ac7129..15ef26d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/TestAudioDevicesFactory.kt
@@ -33,19 +33,22 @@
)
}
- fun wiredDevice(deviceName: String = "wired"): AudioDeviceInfo {
+ fun wiredDevice(
+ deviceName: String = "wired",
+ deviceAddress: String = "card=1;device=0",
+ ): AudioDeviceInfo {
return AudioDeviceInfo(
AudioDevicePort.createForTesting(
AudioDeviceInfo.TYPE_WIRED_HEADPHONES,
deviceName,
- "",
+ deviceAddress,
)
)
}
fun bluetoothDevice(
deviceName: String = "bt",
- deviceAddress: String = "test_address",
+ deviceAddress: String = "00:43:A8:23:10:F0",
): AudioDeviceInfo {
return AudioDeviceInfo(
AudioDevicePort.createForTesting(
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
index 3f51a79..e2d414e 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
@@ -16,6 +16,7 @@
package com.android.systemui.volume.domain.interactor
+import android.content.applicationContext
import com.android.systemui.bluetooth.bluetoothAdapter
import com.android.systemui.bluetooth.localBluetoothManager
import com.android.systemui.kosmos.Kosmos
@@ -27,6 +28,7 @@
val Kosmos.audioOutputInteractor by
Kosmos.Fixture {
AudioOutputInteractor(
+ applicationContext,
audioRepository,
audioModeInteractor,
testScope.backgroundScope,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt
index 9c902cf..680535d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/data/repository/FakeLocalMediaRepositoryFactory.kt
@@ -17,6 +17,7 @@
package com.android.systemui.volume.panel.component.mediaoutput.data.repository
import com.android.settingslib.volume.data.repository.LocalMediaRepository
+import kotlinx.coroutines.CoroutineScope
class FakeLocalMediaRepositoryFactory(private val defaultProvider: () -> LocalMediaRepository) :
LocalMediaRepositoryFactory {
@@ -27,6 +28,8 @@
repositories[packageName] = localMediaRepository
}
- override fun create(packageName: String?): LocalMediaRepository =
- repositories[packageName] ?: defaultProvider()
+ override fun create(
+ packageName: String?,
+ coroutineScope: CoroutineScope
+ ): LocalMediaRepository = repositories[packageName] ?: defaultProvider()
}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
new file mode 100644
index 0000000..9f11822
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.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.volume.panel.component.mediaoutput.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.domain.interactor.audioModeInteractor
+import com.android.systemui.volume.domain.interactor.audioOutputInteractor
+import com.android.systemui.volume.mediaDeviceSessionInteractor
+import com.android.systemui.volume.mediaOutputInteractor
+
+val Kosmos.mediaOutputComponentInteractor by
+ Kosmos.Fixture {
+ MediaOutputComponentInteractor(
+ testScope.backgroundScope,
+ mediaDeviceSessionInteractor,
+ audioOutputInteractor,
+ audioModeInteractor,
+ mediaOutputInteractor,
+ )
+ }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt
index 6d4576e..2cd6ff2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/ui/viewmodel/MediaOutputViewModelKosmos.kt
@@ -20,11 +20,8 @@
import com.android.internal.logging.uiEventLogger
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.testScope
-import com.android.systemui.volume.domain.interactor.audioModeInteractor
-import com.android.systemui.volume.domain.interactor.audioOutputInteractor
-import com.android.systemui.volume.mediaDeviceSessionInteractor
import com.android.systemui.volume.mediaOutputActionsInteractor
-import com.android.systemui.volume.mediaOutputInteractor
+import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.mediaOutputComponentInteractor
var Kosmos.mediaOutputViewModel by
Kosmos.Fixture {
@@ -32,10 +29,7 @@
applicationContext,
testScope.backgroundScope,
mediaOutputActionsInteractor,
- mediaDeviceSessionInteractor,
- audioOutputInteractor,
- audioModeInteractor,
- mediaOutputInteractor,
+ mediaOutputComponentInteractor,
uiEventLogger,
)
}
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 004f37c..2c4bc7c 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -2514,7 +2514,7 @@
public Plane[] getPlanes() {
throwISEIfImageIsInvalid();
if (mPlanes == null) {
- int fenceFd = mParcelImage.fence != null ? mParcelImage.fence.getFd() : -1;
+ int fenceFd = mParcelImage.fence != null ? mParcelImage.fence.detachFd() : -1;
mGraphicBuffer = GraphicBuffer.createFromHardwareBuffer(mParcelImage.buffer);
mPlanes = ImageReader.initializeImagePlanes(mParcelImage.planeCount, mGraphicBuffer,
fenceFd, mParcelImage.format, mParcelImage.timestamp,
diff --git a/packages/services/VirtualCamera/OWNERS b/packages/services/VirtualCamera/OWNERS
deleted file mode 100644
index c66443f..0000000
--- a/packages/services/VirtualCamera/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-include /services/companion/java/com/android/server/companion/virtual/OWNERS
-caen@google.com
-jsebechlebsky@google.com
\ No newline at end of file
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 42f168b..7342b00 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1379,6 +1379,30 @@
}
}
+ @RequiresNoPermission
+ @Override
+ public boolean isMagnificationSystemUIConnected() {
+ if (svcConnTracingEnabled()) {
+ logTraceSvcConn("isMagnificationSystemUIConnected", "");
+ }
+ synchronized (mLock) {
+ if (!hasRightsToCurrentUserLocked()) {
+ return false;
+ }
+ if (!mSecurityPolicy.canControlMagnification(this)) {
+ return false;
+ }
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ MagnificationProcessor magnificationProcessor =
+ mSystemSupport.getMagnificationProcessor();
+ return magnificationProcessor.isMagnificationSystemUIConnected();
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+ }
+
public boolean isMagnificationCallbackEnabled(int displayId) {
return mInvocationHandler.isMagnificationCallbackEnabled(displayId);
}
@@ -1724,9 +1748,7 @@
}
public void resetLocked() {
- if (Flags.resettableDynamicProperties()) {
- mAccessibilityServiceInfo.resetDynamicallyConfigurableProperties();
- }
+ mAccessibilityServiceInfo.resetDynamicallyConfigurableProperties();
mSystemSupport.getKeyEventDispatcher().flush(this);
try {
// Clear the proxy in the other process so this
@@ -1925,6 +1947,11 @@
InvocationHandler.MSG_CLEAR_ACCESSIBILITY_CACHE);
}
+ public void notifyMagnificationSystemUIConnectionChangedLocked(boolean connected) {
+ mInvocationHandler
+ .notifyMagnificationSystemUIConnectionChangedLocked(connected);
+ }
+
public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
@NonNull MagnificationConfig config) {
mInvocationHandler
@@ -1976,6 +2003,21 @@
return (mGenericMotionEventSources & eventSourceWithoutClass) != 0;
}
+ /**
+ * Called by the invocation handler to notify the service that the
+ * magnification systemui connection has changed.
+ */
+ private void notifyMagnificationSystemUIConnectionChangedInternal(boolean connected) {
+ final IAccessibilityServiceClient listener = getServiceInterfaceSafely();
+ if (listener != null) {
+ try {
+ listener.onMagnificationSystemUIConnectionChanged(connected);
+ } catch (RemoteException re) {
+ Slog.e(LOG_TAG,
+ "Error sending magnification sysui connection changes to " + mService, re);
+ }
+ }
+ }
/**
* Called by the invocation handler to notify the service that the
@@ -2372,6 +2414,7 @@
private static final int MSG_BIND_INPUT = 12;
private static final int MSG_UNBIND_INPUT = 13;
private static final int MSG_START_INPUT = 14;
+ private static final int MSG_ON_MAGNIFICATION_SYSTEM_UI_CONNECTION_CHANGED = 15;
/** List of magnification callback states, mapping from displayId -> Boolean */
@GuardedBy("mlock")
@@ -2398,6 +2441,13 @@
notifyClearAccessibilityCacheInternal();
} break;
+ case MSG_ON_MAGNIFICATION_SYSTEM_UI_CONNECTION_CHANGED: {
+ final SomeArgs args = (SomeArgs) message.obj;
+ final boolean connected = args.argi1 == 1;
+ notifyMagnificationSystemUIConnectionChangedInternal(connected);
+ args.recycle();
+ } break;
+
case MSG_ON_MAGNIFICATION_CHANGED: {
final SomeArgs args = (SomeArgs) message.obj;
final Region region = (Region) args.arg1;
@@ -2455,6 +2505,15 @@
}
}
+ public void notifyMagnificationSystemUIConnectionChangedLocked(boolean connected) {
+ final SomeArgs args = SomeArgs.obtain();
+ args.argi1 = connected ? 1 : 0;
+
+ final Message msg =
+ obtainMessage(MSG_ON_MAGNIFICATION_SYSTEM_UI_CONNECTION_CHANGED, args);
+ msg.sendToTarget();
+ }
+
public void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
@NonNull MagnificationConfig config) {
synchronized (mLock) {
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 20b727c..d09cb3e 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -1812,6 +1812,17 @@
}
/**
+ * Called by the MagnificationController when the magnification systemui connection changes.
+ *
+ * @param connected Whether the connection is ready.
+ */
+ public void notifyMagnificationSystemUIConnectionChanged(boolean connected) {
+ synchronized (mLock) {
+ notifyMagnificationSystemUIConnectionChangedLocked(connected);
+ }
+ }
+
+ /**
* Called by the MagnificationController when the state of display
* magnification changes.
*
@@ -2243,6 +2254,14 @@
mProxyManager.clearCacheLocked();
}
+ private void notifyMagnificationSystemUIConnectionChangedLocked(boolean connected) {
+ final AccessibilityUserState state = getCurrentUserStateLocked();
+ for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+ final AccessibilityServiceConnection service = state.mBoundServices.get(i);
+ service.notifyMagnificationSystemUIConnectionChangedLocked(connected);
+ }
+ }
+
private void notifyMagnificationChangedLocked(int displayId, @NonNull Region region,
@NonNull MagnificationConfig config) {
final AccessibilityUserState state = getCurrentUserStateLocked();
@@ -5253,13 +5272,9 @@
//Clip to the window bounds.
Rect windowBounds = mTempRect1;
- if (Flags.focusClickPointWindowBoundsFromA11yWindowInfo()) {
- AccessibilityWindowInfo window = focus.getWindow();
- if (window != null) {
- window.getBoundsInScreen(windowBounds);
- }
- } else {
- getWindowBounds(focus.getWindowId(), windowBounds);
+ AccessibilityWindowInfo window = focus.getWindow();
+ if (window != null) {
+ window.getBoundsInScreen(windowBounds);
}
if (!boundsInScreenBeforeMagnification.intersect(windowBounds)) {
return false;
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index 4cb3d24..420bac7 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -489,6 +489,14 @@
/** @throws UnsupportedOperationException since a proxy does not need magnification */
@RequiresNoPermission
@Override
+ public boolean isMagnificationSystemUIConnected() throws UnsupportedOperationException {
+ throw new UnsupportedOperationException("isMagnificationSystemUIConnected is not"
+ + " supported");
+ }
+
+ /** @throws UnsupportedOperationException since a proxy does not need magnification */
+ @RequiresNoPermission
+ @Override
public boolean isMagnificationCallbackEnabled(int displayId) {
throw new UnsupportedOperationException("isMagnificationCallbackEnabled is not supported");
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
index 0719eba..7f4c808 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
@@ -126,6 +126,7 @@
@ConnectionState
private int mConnectionState = DISCONNECTED;
+ ConnectionStateChangedCallback mConnectionStateChangedCallback = null;
private static final int WAIT_CONNECTION_TIMEOUT_MILLIS = 100;
@@ -264,6 +265,9 @@
}
}
}
+ if (mConnectionStateChangedCallback != null) {
+ mConnectionStateChangedCallback.onConnectionStateChanged(connection != null);
+ }
}
/**
@@ -271,7 +275,7 @@
*/
public boolean isConnected() {
synchronized (mLock) {
- return mConnectionWrapper != null;
+ return mConnectionWrapper != null && mConnectionState == CONNECTED;
}
}
@@ -1344,4 +1348,8 @@
}
}
}
+
+ interface ConnectionStateChangedCallback {
+ void onConnectionStateChanged(boolean connected);
+ }
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
index 76367a2..9b78847 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationController.java
@@ -828,6 +828,8 @@
mMagnificationConnectionManager = new MagnificationConnectionManager(mContext,
mLock, this, mAms.getTraceManager(),
mScaleProvider);
+ mMagnificationConnectionManager.mConnectionStateChangedCallback =
+ mAms::notifyMagnificationSystemUIConnectionChanged;
}
return mMagnificationConnectionManager;
}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
index ed8f1ab..6036839 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationProcessor.java
@@ -147,6 +147,10 @@
return false;
}
+ public boolean isMagnificationSystemUIConnected() {
+ return mController.getMagnificationConnectionManager().isConnected();
+ }
+
private boolean setScaleAndCenterForFullScreenMagnification(int displayId, float scale,
float centerX, float centerY, boolean animate, int id) {
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 0d0c21d..30e4a3e 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -34,6 +34,7 @@
import static com.android.server.companion.utils.PackageUtils.enforceUsesCompanionDeviceFeature;
import static com.android.server.companion.utils.PackageUtils.getPackageInfo;
import static com.android.server.companion.utils.PackageUtils.isRestrictedSettingsAllowed;
+import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageCompanionDevice;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOr;
import static com.android.server.companion.utils.PermissionsUtils.enforceCallerIsSystemOrCanInteractWithUserId;
@@ -334,6 +335,12 @@
enforceCallerCanManageAssociationsForPackage(getContext(), userId, packageName,
"get associations");
+ if (!checkCallerCanManageCompanionDevice(getContext())) {
+ // If the caller neither is system nor holds MANAGE_COMPANION_DEVICES: it needs to
+ // request the feature (also: the caller is the app itself).
+ enforceUsesCompanionDeviceFeature(getContext(), userId, packageName);
+ }
+
return mAssociationStore.getActiveAssociationsByPackage(userId, packageName);
}
diff --git a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
index 3fbd856..d09d7e6 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationRequestsProcessor.java
@@ -347,8 +347,6 @@
* Set association tag.
*/
public void setAssociationTag(int associationId, String tag) {
- Slog.i(TAG, "Setting association tag=[" + tag + "] to id=[" + associationId + "]...");
-
AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
associationId);
association = (new AssociationInfo.Builder(association)).setTag(tag).build();
diff --git a/services/companion/java/com/android/server/companion/association/AssociationStore.java b/services/companion/java/com/android/server/companion/association/AssociationStore.java
index 757abd9..29e8095 100644
--- a/services/companion/java/com/android/server/companion/association/AssociationStore.java
+++ b/services/companion/java/com/android/server/companion/association/AssociationStore.java
@@ -18,7 +18,7 @@
import static com.android.server.companion.utils.MetricUtils.logCreateAssociation;
import static com.android.server.companion.utils.MetricUtils.logRemoveAssociation;
-import static com.android.server.companion.utils.PermissionsUtils.enforceCallerCanManageAssociationsForPackage;
+import static com.android.server.companion.utils.PermissionsUtils.checkCallerCanManageAssociationsForPackage;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -457,10 +457,6 @@
/**
* Get association by id with caller checks.
- *
- * If the association is not found, an IllegalArgumentException would be thrown.
- *
- * If the caller can't access the association, a SecurityException would be thrown.
*/
@NonNull
public AssociationInfo getAssociationWithCallerChecks(int associationId) {
@@ -470,9 +466,13 @@
"getAssociationWithCallerChecks() Association id=[" + associationId
+ "] doesn't exist.");
}
- enforceCallerCanManageAssociationsForPackage(mContext, association.getUserId(),
- association.getPackageName(), null);
- return association;
+ if (checkCallerCanManageAssociationsForPackage(mContext, association.getUserId(),
+ association.getPackageName())) {
+ return association;
+ }
+
+ throw new IllegalArgumentException(
+ "The caller can't interact with the association id=[" + associationId + "].");
}
/**
diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
index 6f0baef..8c1116b 100644
--- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
+++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java
@@ -98,6 +98,7 @@
Slog.i(TAG, "Disassociating id=[" + id + "]...");
final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(id);
+
final int userId = association.getUserId();
final String packageName = association.getPackageName();
final String deviceProfile = association.getDeviceProfile();
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index 00e049c..026d29c 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -122,6 +122,7 @@
*/
public boolean isPermissionTransferUserConsented(int associationId) {
mAssociationStore.getAssociationWithCallerChecks(associationId);
+
PermissionSyncRequest request = getPermissionSyncRequest(associationId);
if (request == null) {
return false;
@@ -146,12 +147,12 @@
return null;
}
- Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
- + "] associationId [" + associationId + "]");
-
final AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
associationId);
+ Slog.i(LOG_TAG, "Creating permission sync intent for userId [" + userId
+ + "] associationId [" + associationId + "]");
+
// Create an internal intent to launch the user consent dialog
final Bundle extras = new Bundle();
PermissionSyncRequest request = new PermissionSyncRequest(associationId);
@@ -219,9 +220,7 @@
* Enable perm sync for the association
*/
public void enablePermissionsSync(int associationId) {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
- int userId = association.getUserId();
+ int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId();
PermissionSyncRequest request = new PermissionSyncRequest(associationId);
request.setUserConsented(true);
mSystemDataTransferRequestStore.writeRequest(userId, request);
@@ -231,9 +230,7 @@
* Disable perm sync for the association
*/
public void disablePermissionsSync(int associationId) {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
- int userId = association.getUserId();
+ int userId = mAssociationStore.getAssociationWithCallerChecks(associationId).getUserId();
PermissionSyncRequest request = new PermissionSyncRequest(associationId);
request.setUserConsented(false);
mSystemDataTransferRequestStore.writeRequest(userId, request);
@@ -244,9 +241,8 @@
*/
@Nullable
public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
- int userId = association.getUserId();
+ int userId = mAssociationStore.getAssociationWithCallerChecks(associationId)
+ .getUserId();
List<SystemDataTransferRequest> requests =
mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
associationId);
@@ -263,9 +259,7 @@
*/
public void removePermissionSyncRequest(int associationId) {
Binder.withCleanCallingIdentity(() -> {
- AssociationInfo association = mAssociationStore.getAssociationWithCallerChecks(
- associationId);
- int userId = association.getUserId();
+ int userId = mAssociationStore.getAssociationById(associationId).getUserId();
mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
});
}
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 796d285..f397814 100644
--- a/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
+++ b/services/companion/java/com/android/server/companion/utils/PermissionsUtils.java
@@ -149,6 +149,21 @@
}
/**
+ * Check if the caller is system UID or the provided user.
+ */
+ public static boolean checkCallerIsSystemOr(@UserIdInt int userId,
+ @NonNull String packageName) {
+ final int callingUid = getCallingUid();
+ if (callingUid == SYSTEM_UID) return true;
+
+ if (getCallingUserId() != userId) return false;
+
+ if (!checkPackage(callingUid, packageName)) return false;
+
+ return true;
+ }
+
+ /**
* Check if the calling user id matches the userId, and if the package belongs to
* the calling uid.
*/
@@ -169,30 +184,21 @@
}
/**
+ * Check if the caller holds the necessary permission to manage companion devices.
+ */
+ public static boolean checkCallerCanManageCompanionDevice(@NonNull Context context) {
+ if (getCallingUid() == SYSTEM_UID) return true;
+
+ return context.checkCallingPermission(MANAGE_COMPANION_DEVICES) == PERMISSION_GRANTED;
+ }
+
+ /**
* Require the caller to be able to manage the associations for the package.
*/
public static void enforceCallerCanManageAssociationsForPackage(@NonNull Context context,
@UserIdInt int userId, @NonNull String packageName,
@Nullable String actionDescription) {
- final int callingUid = getCallingUid();
-
- // If the caller is the system
- if (callingUid == SYSTEM_UID) {
- return;
- }
-
- // If caller can manage the package or has the permissions to manage companion devices
- boolean canInteractAcrossUsers = context.checkCallingPermission(INTERACT_ACROSS_USERS)
- == PERMISSION_GRANTED;
- boolean canManageCompanionDevices = context.checkCallingPermission(MANAGE_COMPANION_DEVICES)
- == PERMISSION_GRANTED;
- if (getCallingUserId() == userId) {
- if (checkPackage(callingUid, packageName) || canManageCompanionDevices) {
- return;
- }
- } else if (canInteractAcrossUsers && canManageCompanionDevices) {
- return;
- }
+ if (checkCallerCanManageAssociationsForPackage(context, userId, packageName)) return;
throw new SecurityException("Caller (uid=" + getCallingUid() + ") does not have "
+ "permissions to "
@@ -213,6 +219,25 @@
}
}
+ /**
+ * Check if the caller is either:
+ * <ul>
+ * <li> the package itself
+ * <li> the System ({@link android.os.Process#SYSTEM_UID})
+ * <li> holds {@link Manifest.permission#MANAGE_COMPANION_DEVICES} and, if belongs to a
+ * different user, also holds {@link Manifest.permission#INTERACT_ACROSS_USERS}.
+ * </ul>
+ * @return whether the caller is one of the above.
+ */
+ public static boolean checkCallerCanManageAssociationsForPackage(@NonNull Context context,
+ @UserIdInt int userId, @NonNull String packageName) {
+ if (checkCallerIsSystemOr(userId, packageName)) return true;
+
+ if (!checkCallerCanInteractWithUserId(context, userId)) return false;
+
+ return checkCallerCanManageCompanionDevice(context);
+ }
+
private static boolean checkPackage(@UserIdInt int uid, @NonNull String packageName) {
try {
return getAppOpsService().checkPackage(uid, packageName) == MODE_ALLOWED;
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 215f640..4a99007 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -29,6 +29,7 @@
import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
import static android.content.pm.PackageManager.ACTION_REQUEST_PERMISSIONS;
import static android.companion.virtualdevice.flags.Flags.virtualCameraServiceDiscovery;
+import static android.companion.virtualdevice.flags.Flags.intentInterceptionActionMatchingFix;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
@@ -1478,7 +1479,13 @@
synchronized (mVirtualDeviceLock) {
boolean hasInterceptedIntent = false;
for (Map.Entry<IBinder, IntentFilter> interceptor : mIntentInterceptors.entrySet()) {
- if (interceptor.getValue().match(
+ IntentFilter intentFilter = interceptor.getValue();
+ // Explicitly match the actions because the intent filter will match any intent
+ // without an explicit action. If the intent has no action, then require that there
+ // are no actions specified in the filter either.
+ boolean explicitActionMatch = !intentInterceptionActionMatchingFix()
+ || intent.getAction() != null || intentFilter.countActions() == 0;
+ if (explicitActionMatch && intentFilter.match(
intent.getAction(), intent.getType(), intent.getScheme(), intent.getData(),
intent.getCategories(), TAG) >= 0) {
try {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 0fdf6d0..f1339e9 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -232,7 +232,6 @@
"android.hardware.rebootescrow-V1-java",
"android.hardware.power.stats-V2-java",
"android.hidl.manager-V1.2-java",
- "audio-permission-aidl-java",
"cbor-java",
"com.android.media.audio-aconfig-java",
"icu4j_calendar_astronomer",
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 966478e..a619257 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -138,12 +138,6 @@
static final long DEFAULT_BOOT_LOOP_TRIGGER_WINDOW_MS = TimeUnit.MINUTES.toMillis(10);
- // Time needed to apply mitigation
- private static final String MITIGATION_WINDOW_MS =
- "persist.device_config.configuration.mitigation_window_ms";
- @VisibleForTesting
- static final long DEFAULT_MITIGATION_WINDOW_MS = TimeUnit.SECONDS.toMillis(5);
-
// Threshold level at which or above user might experience significant disruption.
private static final String MAJOR_USER_IMPACT_LEVEL_THRESHOLD =
"persist.device_config.configuration.major_user_impact_level_threshold";
@@ -216,9 +210,6 @@
@GuardedBy("mLock")
private boolean mSyncRequired = false;
- @GuardedBy("mLock")
- private long mLastMitigation = -1000000;
-
@FunctionalInterface
@VisibleForTesting
interface SystemClock {
@@ -409,16 +400,6 @@
Slog.w(TAG, "Could not resolve a list of failing packages");
return;
}
- synchronized (mLock) {
- final long now = mSystemClock.uptimeMillis();
- if (Flags.recoverabilityDetection()) {
- if (now >= mLastMitigation
- && (now - mLastMitigation) < getMitigationWindowMs()) {
- Slog.i(TAG, "Skipping onPackageFailure mitigation");
- return;
- }
- }
- }
mLongTaskHandler.post(() -> {
synchronized (mLock) {
if (mAllObservers.isEmpty()) {
@@ -519,17 +500,10 @@
int currentObserverImpact,
int mitigationCount) {
if (currentObserverImpact < getUserImpactLevelLimit()) {
- synchronized (mLock) {
- mLastMitigation = mSystemClock.uptimeMillis();
- }
currentObserverToNotify.execute(versionedPackage, failureReason, mitigationCount);
}
}
- private long getMitigationWindowMs() {
- return SystemProperties.getLong(MITIGATION_WINDOW_MS, DEFAULT_MITIGATION_WINDOW_MS);
- }
-
/**
* Called when the system server boots. If the system server is detected to be in a boot loop,
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index db4840d..211f952 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -83,8 +83,6 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.ServiceThread;
-import dalvik.annotation.optimization.NeverCompile;
-
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
@@ -100,6 +98,8 @@
import java.util.Random;
import java.util.Set;
+import dalvik.annotation.optimization.NeverCompile;
+
public final class CachedAppOptimizer {
// Flags stored in the DeviceConfig API.
@@ -2633,7 +2633,7 @@
public void binderError(int debugPid, ProcessRecord app, int code, int flags, int err) {
Slog.w(TAG_AM, "pid " + debugPid + " " + (app == null ? "null" : app.processName)
+ " sent binder code " + code + " with flags " + flags
- + " and got error " + err);
+ + " to frozen apps and got error " + err);
// Do nothing if the binder error callback is not enabled.
// That means the frozen apps in a wrong state will be killed when they are unfrozen later.
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 8647750..ab34dd4 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2205,12 +2205,15 @@
!= 0 ? PROCESS_CAPABILITY_FOREGROUND_LOCATION : 0;
if (roForegroundAudioControl()) { // flag check
- final int fgsAudioType = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
- | FOREGROUND_SERVICE_TYPE_CAMERA
- | FOREGROUND_SERVICE_TYPE_MICROPHONE
- | FOREGROUND_SERVICE_TYPE_PHONE_CALL;
- capabilityFromFGS |= (psr.getForegroundServiceTypes() & fgsAudioType) != 0
- ? PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL : 0;
+ // TODO revisit restriction of FOREGROUND_AUDIO_CONTROL when it can be
+ // limited to specific FGS types
+ //final int fgsAudioType = FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
+ // | FOREGROUND_SERVICE_TYPE_CAMERA
+ // | FOREGROUND_SERVICE_TYPE_MICROPHONE
+ // | FOREGROUND_SERVICE_TYPE_PHONE_CALL;
+ //capabilityFromFGS |= (psr.getForegroundServiceTypes() & fgsAudioType) != 0
+ // ? PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL : 0;
+ capabilityFromFGS |= PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL;
}
final boolean enabled = state.getCachedCompatChange(
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 8d7a1c9..8eef71e 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -22,6 +22,8 @@
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_COMPAT;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED;
import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED;
+import static android.os.Process.ROOT_UID;
+import static android.os.Process.SYSTEM_UID;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -422,6 +424,10 @@
})
public static BackgroundStartPrivileges getDefaultBackgroundStartPrivileges(
int callingUid, @Nullable String callingPackage) {
+ if (callingUid == ROOT_UID || callingUid == SYSTEM_UID) {
+ // root and system must always opt in explicitly
+ return BackgroundStartPrivileges.ALLOW_FGS;
+ }
boolean isChangeEnabledForApp = callingPackage != null ? CompatChanges.isChangeEnabled(
DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_SENDER, callingPackage,
UserHandle.getUserHandleForUid(callingUid)) : CompatChanges.isChangeEnabled(
diff --git a/services/core/java/com/android/server/am/ServiceRecord.java b/services/core/java/com/android/server/am/ServiceRecord.java
index 2184340..b9cdf27 100644
--- a/services/core/java/com/android/server/am/ServiceRecord.java
+++ b/services/core/java/com/android/server/am/ServiceRecord.java
@@ -1005,7 +1005,7 @@
if (isForeground || foregroundId != 0) {
pw.print(prefix); pw.print("isForeground="); pw.print(isForeground);
pw.print(" foregroundId="); pw.print(foregroundId);
- pw.printf(" types=%08X", foregroundServiceType);
+ pw.printf(" types=0x%08X", foregroundServiceType);
pw.print(" foregroundNoti="); pw.println(foregroundNoti);
if (isShortFgs() && mShortFgsInfo != null) {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index 1dc1846..1d21ccb 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -15,6 +15,8 @@
*/
package com.android.server.audio;
+import static android.media.audio.Flags.scoManagedByAudio;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.compat.CompatChanges;
@@ -54,6 +56,7 @@
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
+import android.sysprop.BluetoothProperties;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
@@ -74,7 +77,6 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
-
/**
* @hide
* (non final for mocking/spying)
@@ -167,6 +169,15 @@
@EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.S_V2)
public static final long USE_SET_COMMUNICATION_DEVICE = 243827847L;
+ /** Indicates if headset profile connection and SCO audio control use the new implementation
+ * aligned with other BT profiles. True if both the feature flag Flags.scoManagedByAudio() and
+ * the system property audio.sco.managed.by.audio are true.
+ */
+ private final boolean mScoManagedByAudio;
+ /*package*/ boolean isScoManagedByAudio() {
+ return mScoManagedByAudio;
+ }
+
//-------------------------------------------------------------------
/*package*/ AudioDeviceBroker(@NonNull Context context, @NonNull AudioService service,
@NonNull AudioSystemAdapter audioSystem) {
@@ -176,7 +187,8 @@
mDeviceInventory = new AudioDeviceInventory(this);
mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext);
mAudioSystem = audioSystem;
-
+ mScoManagedByAudio = scoManagedByAudio()
+ && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false);
init();
}
@@ -192,7 +204,8 @@
mDeviceInventory = mockDeviceInventory;
mSystemServer = mockSystemServer;
mAudioSystem = audioSystem;
-
+ mScoManagedByAudio = scoManagedByAudio()
+ && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false);
init();
}
@@ -400,24 +413,24 @@
if (client == null) {
return;
}
-
- boolean isBtScoRequested = isBluetoothScoRequested();
- if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) {
- if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
- Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: "
- + uid);
- // clean up or restore previous client selection
- if (prevClientDevice != null) {
- addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
- } else {
- removeCommunicationRouteClient(cb, true);
+ if (!mScoManagedByAudio) {
+ boolean isBtScoRequested = isBluetoothScoRequested();
+ if (isBtScoRequested && (!wasBtScoRequested || !isBluetoothScoActive())) {
+ if (!mBtHelper.startBluetoothSco(scoAudioMode, eventSource)) {
+ Log.w(TAG, "setCommunicationRouteForClient: failure to start BT SCO for uid: "
+ + uid);
+ // clean up or restore previous client selection
+ if (prevClientDevice != null) {
+ addCommunicationRouteClient(cb, uid, prevClientDevice, prevPrivileged);
+ } else {
+ removeCommunicationRouteClient(cb, true);
+ }
+ postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
}
- postBroadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+ } else if (!isBtScoRequested && wasBtScoRequested) {
+ mBtHelper.stopBluetoothSco(eventSource);
}
- } else if (!isBtScoRequested && wasBtScoRequested) {
- mBtHelper.stopBluetoothSco(eventSource);
}
-
// In BT classic for communication, the device changes from a2dp to sco device, but for
// LE Audio it stays the same and we must trigger the proper stream volume alignment, if
// LE Audio communication device is activated after the audio system has already switched to
@@ -1685,6 +1698,8 @@
pw.println("\n" + prefix + "mAudioModeOwner: " + mAudioModeOwner);
+ pw.println("\n" + prefix + "mScoManagedByAudio: " + mScoManagedByAudio);
+
mBtHelper.dump(pw, prefix);
}
@@ -1837,10 +1852,10 @@
? mAudioService.getBluetoothContextualVolumeStream()
: AudioSystem.STREAM_DEFAULT);
if (btInfo.mProfile == BluetoothProfile.LE_AUDIO
- || btInfo.mProfile
- == BluetoothProfile.HEARING_AID) {
- onUpdateCommunicationRouteClient(
- isBluetoothScoRequested(),
+ || btInfo.mProfile == BluetoothProfile.HEARING_AID
+ || (mScoManagedByAudio
+ && btInfo.mProfile == BluetoothProfile.HEADSET)) {
+ onUpdateCommunicationRouteClient(isBluetoothScoRequested(),
"setBluetoothActiveDevice");
}
}
@@ -2511,7 +2526,7 @@
setCommunicationRouteForClient(crc.getBinder(), crc.getUid(), crc.getDevice(),
BtHelper.SCO_MODE_UNDEFINED, crc.isPrivileged(), eventSource);
} else {
- if (!isBluetoothScoRequested() && wasBtScoRequested) {
+ if (!mScoManagedByAudio && !isBluetoothScoRequested() && wasBtScoRequested) {
mBtHelper.stopBluetoothSco(eventSource);
}
updateCommunicationRoute(eventSource);
@@ -2815,4 +2830,5 @@
void clearDeviceInventory() {
mDeviceInventory.clearDeviceInventory();
}
+
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index e0790da..287c92f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -859,6 +859,15 @@
btInfo, streamType, codec, "onSetBtActiveDevice");
}
break;
+ case BluetoothProfile.HEADSET:
+ if (mDeviceBroker.isScoManagedByAudio()) {
+ if (switchToUnavailable) {
+ mDeviceBroker.onSetBtScoActiveDevice(null);
+ } else if (switchToAvailable) {
+ mDeviceBroker.onSetBtScoActiveDevice(btInfo.mDevice);
+ }
+ }
+ break;
default: throw new IllegalArgumentException("Invalid profile "
+ BluetoothProfile.getProfileName(btInfo.mProfile));
}
diff --git a/services/core/java/com/android/server/audio/AudioPolicyFacade.java b/services/core/java/com/android/server/audio/AudioPolicyFacade.java
index 02e80d6..f652b33 100644
--- a/services/core/java/com/android/server/audio/AudioPolicyFacade.java
+++ b/services/core/java/com/android/server/audio/AudioPolicyFacade.java
@@ -16,12 +16,14 @@
package com.android.server.audio;
+import com.android.media.permission.INativePermissionController;
/**
* Facade to IAudioPolicyService which fulfills AudioService dependencies.
* See @link{IAudioPolicyService.aidl}
*/
public interface AudioPolicyFacade {
-
public boolean isHotwordStreamSupported(boolean lookbackAudio);
+ public INativePermissionController getPermissionController();
+ public void registerOnStartTask(Runnable r);
}
diff --git a/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
new file mode 100644
index 0000000..5ea3c4b
--- /dev/null
+++ b/services/core/java/com/android/server/audio/AudioServerPermissionProvider.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.audio;
+
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.media.permission.INativePermissionController;
+import com.android.media.permission.UidPackageState;
+import com.android.server.pm.pkg.PackageState;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+/** Responsible for synchronizing system server permission state to the native audioserver. */
+public class AudioServerPermissionProvider {
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private INativePermissionController mDest;
+
+ @GuardedBy("mLock")
+ private final Map<Integer, Set<String>> mPackageMap;
+
+ /**
+ * @param appInfos - PackageState for all apps on the device, used to populate init state
+ */
+ public AudioServerPermissionProvider(Collection<PackageState> appInfos) {
+ // Initialize the package state
+ mPackageMap = generatePackageMappings(appInfos);
+ }
+
+ /**
+ * Called whenever audioserver starts (or started before us)
+ *
+ * @param pc - The permission controller interface from audioserver, which we push updates to
+ */
+ public void onServiceStart(@Nullable INativePermissionController pc) {
+ if (pc == null) return;
+ synchronized (mLock) {
+ mDest = pc;
+ resetNativePackageState();
+ }
+ }
+
+ /**
+ * Called when a package is added or removed
+ *
+ * @param uid - uid of modified package (only app-id matters)
+ * @param packageName - the (new) packageName
+ * @param isRemove - true if the package is being removed, false if it is being added
+ */
+ public void onModifyPackageState(int uid, String packageName, boolean isRemove) {
+ // No point in maintaining package mappings for uids of different users
+ uid = UserHandle.getAppId(uid);
+ synchronized (mLock) {
+ // Update state
+ Set<String> packages;
+ if (!isRemove) {
+ packages = mPackageMap.computeIfAbsent(uid, unused -> new ArraySet(1));
+ packages.add(packageName);
+ } else {
+ packages = mPackageMap.get(uid);
+ if (packages != null) {
+ packages.remove(packageName);
+ if (packages.isEmpty()) mPackageMap.remove(uid);
+ }
+ }
+ // Push state to destination
+ if (mDest == null) {
+ return;
+ }
+ var state = new UidPackageState();
+ state.uid = uid;
+ state.packageNames = packages != null ? List.copyOf(packages) : Collections.emptyList();
+ try {
+ mDest.updatePackagesForUid(state);
+ } catch (RemoteException e) {
+ // We will re-init the state when the service comes back up
+ mDest = null;
+ }
+ }
+ }
+
+ /** Called when full syncing package state to audioserver. */
+ @GuardedBy("mLock")
+ private void resetNativePackageState() {
+ if (mDest == null) return;
+ List<UidPackageState> states =
+ mPackageMap.entrySet().stream()
+ .map(
+ entry -> {
+ UidPackageState state = new UidPackageState();
+ state.uid = entry.getKey();
+ state.packageNames = List.copyOf(entry.getValue());
+ return state;
+ })
+ .toList();
+ try {
+ mDest.populatePackagesForUids(states);
+ } catch (RemoteException e) {
+ // We will re-init the state when the service comes back up
+ mDest = null;
+ }
+ }
+
+ /**
+ * Aggregation operation on all package states list: groups by states by app-id and merges the
+ * packageName for each state into an ArraySet.
+ */
+ private static Map<Integer, Set<String>> generatePackageMappings(
+ Collection<PackageState> appInfos) {
+ Collector<PackageState, Object, Set<String>> reducer =
+ Collectors.mapping(
+ (PackageState p) -> p.getPackageName(),
+ Collectors.toCollection(() -> new ArraySet(1)));
+
+ return appInfos.stream()
+ .collect(
+ Collectors.groupingBy(
+ /* predicate */ (PackageState p) -> p.getAppId(),
+ /* factory */ HashMap::new,
+ /* downstream collector */ reducer));
+ }
+}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index cb0ad78..ef65b25 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -31,6 +31,10 @@
import static android.Manifest.permission.QUERY_AUDIO_STATE;
import static android.Manifest.permission.WRITE_SETTINGS;
import static android.app.BroadcastOptions.DELIVERY_GROUP_POLICY_MOST_RECENT;
+import static android.content.Intent.ACTION_PACKAGE_ADDED;
+import static android.content.Intent.ACTION_PACKAGE_REMOVED;
+import static android.content.Intent.EXTRA_ARCHIVAL;
+import static android.content.Intent.EXTRA_REPLACING;
import static android.media.AudioDeviceInfo.TYPE_BLE_HEADSET;
import static android.media.AudioDeviceInfo.TYPE_BLE_SPEAKER;
import static android.media.AudioDeviceInfo.TYPE_BLUETOOTH_A2DP;
@@ -48,6 +52,7 @@
import static android.media.audio.Flags.featureSpatialAudioHeadtrackingLowLatency;
import static android.media.audio.Flags.focusFreezeTestApi;
import static android.media.audio.Flags.roForegroundAudioControl;
+import static android.media.audio.Flags.scoManagedByAudio;
import static android.media.audiopolicy.Flags.enableFadeManagerConfiguration;
import static android.os.Process.FIRST_APPLICATION_UID;
import static android.os.Process.INVALID_UID;
@@ -56,7 +61,9 @@
import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+import static com.android.media.audio.Flags.absVolumeIndexFix;
import static com.android.media.audio.Flags.alarmMinVolumeZero;
+import static com.android.media.audio.Flags.audioserverPermissions;
import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume;
import static com.android.media.audio.Flags.ringerModeAffectsAlarm;
import static com.android.media.audio.Flags.setStreamVolumeOrder;
@@ -238,15 +245,18 @@
import com.android.internal.util.DumpUtils;
import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
+import com.android.server.LocalManagerRegistry;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.audio.AudioServiceEvents.DeviceVolumeEvent;
import com.android.server.audio.AudioServiceEvents.PhoneStateEvent;
import com.android.server.audio.AudioServiceEvents.VolChangedBroadcastEvent;
import com.android.server.audio.AudioServiceEvents.VolumeEvent;
+import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.UserManagerInternal;
import com.android.server.pm.UserManagerInternal.UserRestrictionsListener;
import com.android.server.pm.UserManagerService;
+import com.android.server.pm.pkg.PackageState;
import com.android.server.utils.EventLogger;
import com.android.server.wm.ActivityTaskManagerInternal;
@@ -271,6 +281,7 @@
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BooleanSupplier;
@@ -301,6 +312,8 @@
private final SettingsAdapter mSettings;
private final AudioPolicyFacade mAudioPolicy;
+ private final AudioServerPermissionProvider mPermissionProvider;
+
private final MusicFxHelper mMusicFxHelper;
/** Debug audio mode */
@@ -631,6 +644,17 @@
// If absolute volume is supported in AVRCP device
private volatile boolean mAvrcpAbsVolSupported = false;
+ private final Object mCachedAbsVolDrivingStreamsLock = new Object();
+ // Contains for all the device types which support absolute volume the current streams that
+ // are driving the volume changes
+ @GuardedBy("mCachedAbsVolDrivingStreamsLock")
+ private final HashMap<Integer, Integer> mCachedAbsVolDrivingStreams = new HashMap<>(
+ Map.of(AudioSystem.DEVICE_OUT_BLE_HEADSET, AudioSystem.STREAM_MUSIC,
+ AudioSystem.DEVICE_OUT_BLE_SPEAKER, AudioSystem.STREAM_MUSIC,
+ AudioSystem.DEVICE_OUT_BLE_BROADCAST, AudioSystem.STREAM_MUSIC,
+ AudioSystem.DEVICE_OUT_HEARING_AID, AudioSystem.STREAM_MUSIC
+ ));
+
/**
* Default stream type used for volume control in the absence of playback
* e.g. user on homescreen, no app playing anything, presses hardware volume buttons, this
@@ -1008,14 +1032,22 @@
public Lifecycle(Context context) {
super(context);
+ var audioserverLifecycleExecutor = Executors.newSingleThreadExecutor();
+ var audioPolicyFacade = new DefaultAudioPolicyFacade(audioserverLifecycleExecutor);
mService = new AudioService(context,
AudioSystemAdapter.getDefaultAdapter(),
SystemServerAdapter.getDefaultAdapter(context),
SettingsAdapter.getDefaultAdapter(),
new AudioVolumeGroupHelper(),
- new DefaultAudioPolicyFacade(r -> r.run()),
- null);
-
+ audioPolicyFacade,
+ null,
+ context.getSystemService(AppOpsManager.class),
+ PermissionEnforcer.fromContext(context),
+ audioserverPermissions() ?
+ initializeAudioServerPermissionProvider(
+ context, audioPolicyFacade, audioserverLifecycleExecutor) :
+ null
+ );
}
@Override
@@ -1092,25 +1124,6 @@
/**
* @param context
* @param audioSystem Adapter for {@link AudioSystem}
- * @param systemServer Adapter for privileged functionality for system server components
- * @param settings Adapter for {@link Settings}
- * @param audioVolumeGroupHelper Adapter for {@link AudioVolumeGroup}
- * @param audioPolicy Interface of a facade to IAudioPolicyManager
- * @param looper Looper to use for the service's message handler. If this is null, an
- * {@link AudioSystemThread} is created as the messaging thread instead.
- */
- public AudioService(Context context, AudioSystemAdapter audioSystem,
- SystemServerAdapter systemServer, SettingsAdapter settings,
- AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
- @Nullable Looper looper) {
- this (context, audioSystem, systemServer, settings, audioVolumeGroupHelper,
- audioPolicy, looper, context.getSystemService(AppOpsManager.class),
- PermissionEnforcer.fromContext(context));
- }
-
- /**
- * @param context
- * @param audioSystem Adapter for {@link AudioSystem}
* @param systemServer Adapter for privilieged functionality for system server components
* @param settings Adapter for {@link Settings}
* @param audioVolumeGroupHelper Adapter for {@link AudioVolumeGroup}
@@ -1124,13 +1137,16 @@
public AudioService(Context context, AudioSystemAdapter audioSystem,
SystemServerAdapter systemServer, SettingsAdapter settings,
AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
- @Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer) {
+ @Nullable Looper looper, AppOpsManager appOps, @NonNull PermissionEnforcer enforcer,
+ /* @NonNull */ AudioServerPermissionProvider permissionProvider) {
super(enforcer);
sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
mContext = context;
mContentResolver = context.getContentResolver();
mAppOps = appOps;
+ mPermissionProvider = permissionProvider;
+
mAudioSystem = audioSystem;
mSystemServer = systemServer;
mAudioVolumeGroupHelper = audioVolumeGroupHelper;
@@ -1471,6 +1487,13 @@
// check on volume initialization
checkVolumeRangeInitialization("AudioService()");
+
+ synchronized (mCachedAbsVolDrivingStreamsLock) {
+ mCachedAbsVolDrivingStreams.forEach((dev, stream) -> {
+ mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", /*enabled=*/true,
+ stream);
+ });
+ }
}
private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener =
@@ -1503,7 +1526,9 @@
// Register for device connection intent broadcasts.
IntentFilter intentFilter =
new IntentFilter(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
- intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+ if (!mDeviceBroker.isScoManagedByAudio()) {
+ intentFilter.addAction(BluetoothHeadset.ACTION_ACTIVE_DEVICE_CHANGED);
+ }
intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
if (mDisplayManager == null) {
intentFilter.addAction(Intent.ACTION_SCREEN_ON);
@@ -1908,6 +1933,14 @@
}
onIndicateSystemReady();
+
+ synchronized (mCachedAbsVolDrivingStreamsLock) {
+ mCachedAbsVolDrivingStreams.forEach((dev, stream) -> {
+ mAudioSystem.setDeviceAbsoluteVolumeEnabled(dev, /*address=*/"", /*enabled=*/true,
+ stream);
+ });
+ }
+
// indicate the end of reconfiguration phase to audio HAL
AudioSystem.setParameters("restarting=false");
@@ -3734,8 +3767,10 @@
int newIndex = mStreamStates[streamType].getIndex(device);
+ int streamToDriveAbsVol = absVolumeIndexFix() ? getBluetoothContextualVolumeStream() :
+ AudioSystem.STREAM_MUSIC;
// Check if volume update should be send to AVRCP
- if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+ if (streamTypeAlias == streamToDriveAbsVol
&& AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
&& (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
if (DEBUG_VOL) {
@@ -4527,15 +4562,20 @@
+ featureSpatialAudioHeadtrackingLowLatency());
pw.println("\tandroid.media.audio.focusFreezeTestApi:"
+ focusFreezeTestApi());
+ pw.println("\tcom.android.media.audio.audioserverPermissions:"
+ + audioserverPermissions());
pw.println("\tcom.android.media.audio.disablePrescaleAbsoluteVolume:"
+ disablePrescaleAbsoluteVolume());
-
pw.println("\tcom.android.media.audio.setStreamVolumeOrder:"
+ setStreamVolumeOrder());
pw.println("\tandroid.media.audio.roForegroundAudioControl:"
+ roForegroundAudioControl());
+ pw.println("\tandroid.media.audio.scoManagedByAudio:"
+ + scoManagedByAudio());
pw.println("\tcom.android.media.audio.vgsVssSyncMuteOrder:"
+ vgsVssSyncMuteOrder());
+ pw.println("\tcom.android.media.audio.absVolumeIndexFix:"
+ + absVolumeIndexFix());
}
private void dumpAudioMode(PrintWriter pw) {
@@ -4731,7 +4771,9 @@
}
}
- if (streamTypeAlias == AudioSystem.STREAM_MUSIC
+ int streamToDriveAbsVol = absVolumeIndexFix() ? getBluetoothContextualVolumeStream() :
+ AudioSystem.STREAM_MUSIC;
+ if (streamTypeAlias == streamToDriveAbsVol
&& AudioSystem.DEVICE_OUT_ALL_A2DP_SET.contains(device)
&& (flags & AudioManager.FLAG_BLUETOOTH_ABS_VOLUME) == 0) {
if (DEBUG_VOL) {
@@ -6180,6 +6222,17 @@
setLeAudioVolumeOnModeUpdate(mode, device, streamAlias, index, maxIndex);
+ synchronized (mCachedAbsVolDrivingStreamsLock) {
+ mCachedAbsVolDrivingStreams.replaceAll((absDev, stream) -> {
+ int streamToDriveAbs = getBluetoothContextualVolumeStream();
+ if (stream != streamToDriveAbs) {
+ mAudioSystem.setDeviceAbsoluteVolumeEnabled(absDev, /*address=*/
+ "", /*enabled*/true, streamToDriveAbs);
+ }
+ return streamToDriveAbs;
+ });
+ }
+
// when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all SCO
// connections not started by the application changing the mode when pid changes
mDeviceBroker.postSetModeOwner(mode, pid, uid);
@@ -7859,7 +7912,8 @@
if (profile != BluetoothProfile.A2DP && profile != BluetoothProfile.A2DP_SINK
&& profile != BluetoothProfile.LE_AUDIO
&& profile != BluetoothProfile.LE_AUDIO_BROADCAST
- && profile != BluetoothProfile.HEARING_AID) {
+ && profile != BluetoothProfile.HEARING_AID
+ && !(mDeviceBroker.isScoManagedByAudio() && profile == BluetoothProfile.HEADSET)) {
throw new IllegalArgumentException("Illegal BluetoothProfile profile for device "
+ previousDevice + " -> " + newDevice + ". Got: " + profile);
}
@@ -8100,6 +8154,10 @@
return mAudioVolumeGroup.name();
}
+ public int getId() {
+ return mAudioVolumeGroup.getId();
+ }
+
/**
* Volume group with non null minimum index are considered as non mutable, thus
* bijectivity is broken with potential associated stream type.
@@ -8750,24 +8808,30 @@
}
private int getAbsoluteVolumeIndex(int index) {
- /* Special handling for Bluetooth Absolute Volume scenario
- * If we send full audio gain, some accessories are too loud even at its lowest
- * volume. We are not able to enumerate all such accessories, so here is the
- * workaround from phone side.
- * Pre-scale volume at lowest volume steps 1 2 and 3.
- * For volume step 0, set audio gain to 0 as some accessories won't mute on their end.
- */
- if (index == 0) {
- // 0% for volume 0
- index = 0;
- } else if (!disablePrescaleAbsoluteVolume() && index > 0 && index <= 3) {
- // Pre-scale for volume steps 1 2 and 3
- index = (int) (mIndexMax * mPrescaleAbsoluteVolume[index - 1]) / 10;
+ if (absVolumeIndexFix()) {
+ // The attenuation is applied in the APM. No need to manipulate the index here
+ return index;
} else {
- // otherwise, full gain
- index = (mIndexMax + 5) / 10;
+ /* Special handling for Bluetooth Absolute Volume scenario
+ * If we send full audio gain, some accessories are too loud even at its lowest
+ * volume. We are not able to enumerate all such accessories, so here is the
+ * workaround from phone side.
+ * Pre-scale volume at lowest volume steps 1 2 and 3.
+ * For volume step 0, set audio gain to 0 as some accessories won't mute on their
+ * end.
+ */
+ if (index == 0) {
+ // 0% for volume 0
+ index = 0;
+ } else if (!disablePrescaleAbsoluteVolume() && index > 0 && index <= 3) {
+ // Pre-scale for volume steps 1 2 and 3
+ index = (int) (mIndexMax * mPrescaleAbsoluteVolume[index - 1]) / 10;
+ } else {
+ // otherwise, full gain
+ index = (mIndexMax + 5) / 10;
+ }
+ return index;
}
- return index;
}
private void setStreamVolumeIndex(int index, int device) {
@@ -8778,6 +8842,11 @@
&& !isFullyMuted()) {
index = 1;
}
+
+ if (DEBUG_VOL) {
+ Log.d(TAG, "setStreamVolumeIndexAS(" + mStreamType + ", " + index + ", " + device
+ + ")");
+ }
mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, device);
}
@@ -8789,14 +8858,24 @@
} else if (isAbsoluteVolumeDevice(device)
|| isA2dpAbsoluteVolumeDevice(device)
|| AudioSystem.isLeAudioDeviceType(device)) {
- index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
+ // do not change the volume logic for dynamic abs behavior devices like HDMI
+ if (absVolumeIndexFix() && isAbsoluteVolumeDevice(device)) {
+ index = getAbsoluteVolumeIndex((mIndexMax + 5) / 10);
+ } else {
+ index = getAbsoluteVolumeIndex((getIndex(device) + 5) / 10);
+ }
} else if (isFullVolumeDevice(device)) {
index = (mIndexMax + 5)/10;
} else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
- index = (mIndexMax + 5)/10;
+ if (absVolumeIndexFix()) {
+ index = getAbsoluteVolumeIndex((getIndex(device) + 5) / 10);
+ } else {
+ index = (mIndexMax + 5) / 10;
+ }
} else {
index = (getIndex(device) + 5)/10;
}
+
setStreamVolumeIndex(index, device);
}
@@ -8814,11 +8893,22 @@
|| isA2dpAbsoluteVolumeDevice(device)
|| AudioSystem.isLeAudioDeviceType(device)) {
isAbsoluteVolume = true;
- index = getAbsoluteVolumeIndex((getIndex(device) + 5)/10);
+ // do not change the volume logic for dynamic abs behavior devices
+ // like HDMI
+ if (absVolumeIndexFix() && isAbsoluteVolumeDevice(device)) {
+ index = getAbsoluteVolumeIndex((mIndexMax + 5) / 10);
+ } else {
+ index = getAbsoluteVolumeIndex((getIndex(device) + 5) / 10);
+ }
} else if (isFullVolumeDevice(device)) {
index = (mIndexMax + 5)/10;
} else if (device == AudioSystem.DEVICE_OUT_HEARING_AID) {
- index = (mIndexMax + 5)/10;
+ if (absVolumeIndexFix()) {
+ isAbsoluteVolume = true;
+ index = getAbsoluteVolumeIndex((getIndex(device) + 5) / 10);
+ } else {
+ index = (mIndexMax + 5) / 10;
+ }
} else {
index = (mIndexMap.valueAt(i) + 5)/10;
}
@@ -9815,6 +9905,27 @@
/*package*/ void setAvrcpAbsoluteVolumeSupported(boolean support) {
mAvrcpAbsVolSupported = support;
+ if (absVolumeIndexFix()) {
+ int a2dpDev = AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP;
+ synchronized (mCachedAbsVolDrivingStreamsLock) {
+ mCachedAbsVolDrivingStreams.compute(a2dpDev, (dev, stream) -> {
+ if (stream != null && !mAvrcpAbsVolSupported) {
+ mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev, /*address=*/
+ "", /*enabled*/false, AudioSystem.DEVICE_NONE);
+ return null;
+ }
+ // For A2DP and AVRCP we need to set the driving stream based on the
+ // BT contextual stream. Hence, we need to make sure in adjustStreamVolume
+ // and setStreamVolume that the driving abs volume stream is consistent.
+ int streamToDriveAbs = getBluetoothContextualVolumeStream();
+ if (stream == null || stream != streamToDriveAbs) {
+ mAudioSystem.setDeviceAbsoluteVolumeEnabled(a2dpDev, /*address=*/
+ "", /*enabled*/true, streamToDriveAbs);
+ }
+ return streamToDriveAbs;
+ });
+ }
+ }
sendMsg(mAudioHandler, MSG_SET_DEVICE_VOLUME, SENDMSG_QUEUE,
AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, 0,
mStreamStates[AudioSystem.STREAM_MUSIC], 0);
@@ -11831,6 +11942,45 @@
private static final String mMetricsId = MediaMetrics.Name.AUDIO_SERVICE
+ MediaMetrics.SEPARATOR;
+ private static AudioServerPermissionProvider initializeAudioServerPermissionProvider(
+ Context context, AudioPolicyFacade audioPolicy, Executor audioserverExecutor) {
+ Collection<PackageState> packageStates = null;
+ try (PackageManagerLocal.UnfilteredSnapshot snapshot =
+ LocalManagerRegistry.getManager(PackageManagerLocal.class)
+ .withUnfilteredSnapshot()) {
+ packageStates = snapshot.getPackageStates().values();
+ }
+ var provider = new AudioServerPermissionProvider(packageStates);
+ audioPolicy.registerOnStartTask(() -> {
+ provider.onServiceStart(audioPolicy.getPermissionController());
+ });
+
+ // Set up event listeners
+ IntentFilter packageUpdateFilter = new IntentFilter();
+ packageUpdateFilter.addAction(ACTION_PACKAGE_ADDED);
+ packageUpdateFilter.addAction(ACTION_PACKAGE_REMOVED);
+ packageUpdateFilter.addDataScheme("package");
+
+ context.registerReceiverForAllUsers(new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ String pkgName = intent.getData().getEncodedSchemeSpecificPart();
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, Process.INVALID_UID);
+ if (intent.getBooleanExtra(EXTRA_REPLACING, false) ||
+ intent.getBooleanExtra(EXTRA_ARCHIVAL, false)) return;
+ if (action.equals(ACTION_PACKAGE_ADDED)) {
+ audioserverExecutor.execute(() ->
+ provider.onModifyPackageState(uid, pkgName, false /* isRemoved */));
+ } else if (action.equals(ACTION_PACKAGE_REMOVED)) {
+ audioserverExecutor.execute(() ->
+ provider.onModifyPackageState(uid, pkgName, true /* isRemoved */));
+ }
+ }
+ }, packageUpdateFilter, null, null); // main thread is fine, since dispatch on executor
+ return provider;
+ }
+
// Inform AudioFlinger of our device's low RAM attribute
private static void readAndSetLowRamDevice()
{
diff --git a/services/core/java/com/android/server/audio/AudioSystemAdapter.java b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
index 7202fa2..7f4bc74 100644
--- a/services/core/java/com/android/server/audio/AudioSystemAdapter.java
+++ b/services/core/java/com/android/server/audio/AudioSystemAdapter.java
@@ -598,6 +598,21 @@
}
/**
+ * Same as {@link AudioSystem#setDeviceAbsoluteVolumeEnabled(int, String, boolean, int)}
+ * @param nativeDeviceType the internal device type for which absolute volume is
+ * enabled/disabled
+ * @param address the address of the device for which absolute volume is enabled/disabled
+ * @param enabled whether the absolute volume is enabled/disabled
+ * @param streamToDriveAbs the stream that is controlling the absolute volume
+ * @return status of indicating the success of this operation
+ */
+ public int setDeviceAbsoluteVolumeEnabled(int nativeDeviceType, @NonNull String address,
+ boolean enabled, int streamToDriveAbs) {
+ return AudioSystem.setDeviceAbsoluteVolumeEnabled(nativeDeviceType, address, enabled,
+ streamToDriveAbs);
+ }
+
+ /**
* Same as {@link AudioSystem#registerPolicyMixes(ArrayList, boolean)}
* @param mixes
* @param register
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index b1ea85c..6bb3eb1 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -401,50 +401,67 @@
private void onScoAudioStateChanged(int state) {
boolean broadcast = false;
int scoAudioState = AudioManager.SCO_AUDIO_STATE_ERROR;
- switch (state) {
- case BluetoothHeadset.STATE_AUDIO_CONNECTED:
- scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
- && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- } else if (mDeviceBroker.isBluetoothScoRequested()) {
- // broadcast intent if the connection was initated by AudioService
+ if (mDeviceBroker.isScoManagedByAudio()) {
+ switch (state) {
+ case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+ mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged");
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
broadcast = true;
- }
- mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged");
- break;
- case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
- mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged");
- scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
- // There are two cases where we want to immediately reconnect audio:
- // 1) If a new start request was received while disconnecting: this was
- // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
- // 2) If audio was connected then disconnected via Bluetooth APIs and
- // we still have pending activation requests by apps: this is indicated by
- // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested.
- if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
- if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
- && connectBluetoothScoAudioHelper(mBluetoothHeadset,
- mBluetoothHeadsetDevice, mScoAudioMode)) {
- mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
- scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
+ break;
+ case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+ mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged");
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+ broadcast = true;
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (state) {
+ case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+ && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ } else if (mDeviceBroker.isBluetoothScoRequested()) {
+ // broadcast intent if the connection was initated by AudioService
broadcast = true;
- break;
}
- }
- if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
- broadcast = true;
- }
- mScoAudioState = SCO_STATE_INACTIVE;
- break;
- case BluetoothHeadset.STATE_AUDIO_CONNECTING:
- if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
- && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
- mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
- }
- break;
- default:
- break;
+ mDeviceBroker.setBluetoothScoOn(true, "BtHelper.onScoAudioStateChanged");
+ break;
+ case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+ mDeviceBroker.setBluetoothScoOn(false, "BtHelper.onScoAudioStateChanged");
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+ // There are two cases where we want to immediately reconnect audio:
+ // 1) If a new start request was received while disconnecting: this was
+ // notified by requestScoState() setting state to SCO_STATE_ACTIVATE_REQ.
+ // 2) If audio was connected then disconnected via Bluetooth APIs and
+ // we still have pending activation requests by apps: this is indicated by
+ // state SCO_STATE_ACTIVE_EXTERNAL and BT SCO is requested.
+ if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
+ if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
+ && connectBluetoothScoAudioHelper(mBluetoothHeadset,
+ mBluetoothHeadsetDevice, mScoAudioMode)) {
+ mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+ scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTING;
+ broadcast = true;
+ break;
+ }
+ }
+ if (mScoAudioState != SCO_STATE_ACTIVE_EXTERNAL) {
+ broadcast = true;
+ }
+ mScoAudioState = SCO_STATE_INACTIVE;
+ break;
+ case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+ if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL
+ && mScoAudioState != SCO_STATE_DEACTIVATE_REQ) {
+ mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+ }
+ break;
+ default:
+ break;
+ }
}
if (broadcast) {
broadcastScoConnectionState(scoAudioState);
@@ -454,7 +471,6 @@
newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState);
sendStickyBroadcastToAll(newIntent);
}
-
}
/**
*
diff --git a/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java b/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java
index 75febbc..09701e4 100644
--- a/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java
+++ b/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java
@@ -16,10 +16,15 @@
package com.android.server.audio;
+import android.annotation.Nullable;
import android.media.IAudioPolicyService;
+import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
+import com.android.media.permission.INativePermissionController;
+
+import java.util.Objects;
import java.util.concurrent.Executor;
import java.util.function.Function;
@@ -43,6 +48,7 @@
(Function<IBinder, IAudioPolicyService>)
IAudioPolicyService.Stub::asInterface,
e);
+ mServiceHolder.registerOnStartTask(i -> Binder.allowBlocking(i.asBinder()));
}
@Override
@@ -55,4 +61,23 @@
throw new IllegalStateException();
}
}
+
+ @Override
+ public @Nullable INativePermissionController getPermissionController() {
+ IAudioPolicyService ap = mServiceHolder.checkService();
+ if (ap == null) return null;
+ try {
+ var res = Objects.requireNonNull(ap.getPermissionController());
+ Binder.allowBlocking(res.asBinder());
+ return res;
+ } catch (RemoteException e) {
+ mServiceHolder.attemptClear(ap.asBinder());
+ return null;
+ }
+ }
+
+ @Override
+ public void registerOnStartTask(Runnable task) {
+ mServiceHolder.registerOnStartTask(unused -> task.run());
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
index 712dcee..92fd9cb 100644
--- a/services/core/java/com/android/server/biometrics/biometrics.aconfig
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -14,10 +14,3 @@
description: "This flag controls whether virtual HAL is used for testing instead of TestHal "
bug: "294254230"
}
-
-flag {
- name: "mandatory_biometrics"
- namespace: "biometrics_framework"
- description: "This flag controls whether LSKF fallback is removed from biometric prompt when the phone is outside trusted locations"
- bug: "322081563"
-}
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 5690a9e..69bc66f 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -135,7 +135,7 @@
float[] alternativeRefreshRates,
@Display.HdrCapabilities.HdrType int[] supportedHdrTypes) {
return new Display.Mode(NEXT_DISPLAY_MODE_ID.getAndIncrement(), width, height, refreshRate,
- vsyncRate, false, alternativeRefreshRates, supportedHdrTypes);
+ vsyncRate, /* isSynthetic= */ false, alternativeRefreshRates, supportedHdrTypes);
}
public interface Listener {
diff --git a/services/core/java/com/android/server/display/DisplayBrightnessState.java b/services/core/java/com/android/server/display/DisplayBrightnessState.java
index baa154d..184ae41 100644
--- a/services/core/java/com/android/server/display/DisplayBrightnessState.java
+++ b/services/core/java/com/android/server/display/DisplayBrightnessState.java
@@ -47,6 +47,8 @@
private final BrightnessEvent mBrightnessEvent;
private final int mBrightnessAdjustmentFlag;
+ private final boolean mIsUserInitiatedChange;
+
private DisplayBrightnessState(Builder builder) {
mBrightness = builder.getBrightness();
mSdrBrightness = builder.getSdrBrightness();
@@ -60,6 +62,7 @@
mShouldUpdateScreenBrightnessSetting = builder.shouldUpdateScreenBrightnessSetting();
mBrightnessEvent = builder.getBrightnessEvent();
mBrightnessAdjustmentFlag = builder.getBrightnessAdjustmentFlag();
+ mIsUserInitiatedChange = builder.isUserInitiatedChange();
}
/**
@@ -148,6 +151,13 @@
return mBrightnessAdjustmentFlag;
}
+ /**
+ * Gets if the current brightness changes are because of a user initiated change
+ */
+ public boolean isUserInitiatedChange() {
+ return mIsUserInitiatedChange;
+ }
+
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("DisplayBrightnessState:");
@@ -168,6 +178,7 @@
stringBuilder.append("\n mBrightnessEvent:")
.append(Objects.toString(mBrightnessEvent, "null"));
stringBuilder.append("\n mBrightnessAdjustmentFlag:").append(mBrightnessAdjustmentFlag);
+ stringBuilder.append("\n mIsUserInitiatedChange:").append(mIsUserInitiatedChange);
return stringBuilder.toString();
}
@@ -199,7 +210,8 @@
&& mShouldUpdateScreenBrightnessSetting
== otherState.shouldUpdateScreenBrightnessSetting()
&& Objects.equals(mBrightnessEvent, otherState.getBrightnessEvent())
- && mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag();
+ && mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag()
+ && mIsUserInitiatedChange == otherState.isUserInitiatedChange();
}
@Override
@@ -207,7 +219,8 @@
return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason,
mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mMinBrightness,
mCustomAnimationRate,
- mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag);
+ mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag,
+ mIsUserInitiatedChange);
}
/**
@@ -236,6 +249,8 @@
public int mBrightnessAdjustmentFlag = 0;
+ private boolean mIsUserInitiatedChange;
+
/**
* Create a builder starting with the values from the specified {@link
* DisplayBrightnessState}.
@@ -257,6 +272,7 @@
state.shouldUpdateScreenBrightnessSetting());
builder.setBrightnessEvent(state.getBrightnessEvent());
builder.setBrightnessAdjustmentFlag(state.getBrightnessAdjustmentFlag());
+ builder.setIsUserInitiatedChange(state.isUserInitiatedChange());
return builder;
}
@@ -464,5 +480,20 @@
mBrightnessAdjustmentFlag = brightnessAdjustmentFlag;
return this;
}
+
+ /**
+ * Gets if the current change is a user initiated change
+ */
+ public boolean isUserInitiatedChange() {
+ return mIsUserInitiatedChange;
+ }
+
+ /**
+ * This is used to set if the current change is a user initiated change
+ */
+ public Builder setIsUserInitiatedChange(boolean isUserInitiatedChange) {
+ mIsUserInitiatedChange = isUserInitiatedChange;
+ return this;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d4c0b01..5fd0253 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -49,6 +49,8 @@
import static android.provider.Settings.Secure.RESOLUTION_MODE_HIGH;
import static android.provider.Settings.Secure.RESOLUTION_MODE_UNKNOWN;
+import static com.android.server.display.layout.Layout.Display.POSITION_REAR;
+
import android.Manifest;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
@@ -4972,8 +4974,9 @@
}
final DisplayDevice displayDevice = display.getPrimaryDisplayDeviceLocked();
- final boolean ownContent = (displayDevice.getDisplayDeviceInfoLocked().flags
- & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0;
+ final boolean isRearDisplay = display.getDevicePositionLocked() == POSITION_REAR;
+ final boolean ownContent = ((displayDevice.getDisplayDeviceInfoLocked().flags
+ & DisplayDeviceInfo.FLAG_OWN_CONTENT_ONLY) != 0) || isRearDisplay;
// If the display has enabled mirroring, but specified that it will be managed by
// WindowManager, return an invalid display id. This is to ensure we don't
// accidentally select the display id to mirror based on DM logic and instead allow
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index b97053b..8d71c70 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -1391,6 +1391,7 @@
// request changes.
final boolean wasShortTermModelActive =
mAutomaticBrightnessStrategy.isShortTermModelActive();
+ boolean userInitiatedChange = displayBrightnessState.isUserInitiatedChange();
boolean allowAutoBrightnessWhileDozing =
mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig();
if (mFlags.offloadControlsDozeAutoBrightness() && mFlags.isDisplayOffloadEnabled()
@@ -1413,13 +1414,14 @@
mPowerRequest.policy,
mDisplayBrightnessController.getLastUserSetScreenBrightness(),
userSetBrightnessChanged);
+
+ // If the brightness is already set then it's been overridden by something other than
+ // the user, or is a temporary adjustment.
+ userInitiatedChange = (Float.isNaN(brightnessState))
+ && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
+ || userSetBrightnessChanged);
}
- // If the brightness is already set then it's been overridden by something other than the
- // user, or is a temporary adjustment.
- boolean userInitiatedChange = (Float.isNaN(brightnessState))
- && (mAutomaticBrightnessStrategy.getAutoBrightnessAdjustmentChanged()
- || userSetBrightnessChanged);
final int autoBrightnessState = mAutomaticBrightnessStrategy.isAutoBrightnessEnabled()
? AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 182b05a..44846f3 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -168,6 +168,12 @@
}
SurfaceControl.DesiredDisplayModeSpecs modeSpecs =
mSurfaceControlProxy.getDesiredDisplayModeSpecs(displayToken);
+ if (modeSpecs == null) {
+ // If mode specs is null, it most probably means that display got
+ // unplugged very rapidly.
+ Slog.w(TAG, "Desired display mode specs from SurfaceFlinger are null");
+ return;
+ }
LocalDisplayDevice device = mDevices.get(physicalDisplayId);
if (device == null) {
// Display was added.
diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
index aa17df6..d567331 100644
--- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
+++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java
@@ -600,6 +600,7 @@
private StrategyExecutionRequest constructStrategyExecutionRequest(
DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) {
float currentScreenBrightness = getCurrentBrightness();
- return new StrategyExecutionRequest(displayPowerRequest, currentScreenBrightness);
+ return new StrategyExecutionRequest(displayPowerRequest, currentScreenBrightness,
+ mUserSetScreenBrightnessUpdated);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java
index 82c0bbf..304640b 100644
--- a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java
+++ b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java
@@ -29,10 +29,14 @@
private final float mCurrentScreenBrightness;
+ // Represents if the user set screen brightness was changed or not.
+ private boolean mUserSetBrightnessChanged;
+
public StrategyExecutionRequest(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest,
- float currentScreenBrightness) {
+ float currentScreenBrightness, boolean userSetBrightnessChanged) {
mDisplayPowerRequest = displayPowerRequest;
mCurrentScreenBrightness = currentScreenBrightness;
+ mUserSetBrightnessChanged = userSetBrightnessChanged;
}
public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() {
@@ -43,6 +47,10 @@
return mCurrentScreenBrightness;
}
+ public boolean isUserSetBrightnessChanged() {
+ return mUserSetBrightnessChanged;
+ }
+
@Override
public boolean equals(Object obj) {
if (!(obj instanceof StrategyExecutionRequest)) {
@@ -50,11 +58,13 @@
}
StrategyExecutionRequest other = (StrategyExecutionRequest) obj;
return Objects.equals(mDisplayPowerRequest, other.getDisplayPowerRequest())
- && mCurrentScreenBrightness == other.getCurrentScreenBrightness();
+ && mCurrentScreenBrightness == other.getCurrentScreenBrightness()
+ && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged();
}
@Override
public int hashCode() {
- return Objects.hash(mDisplayPowerRequest, mCurrentScreenBrightness);
+ return Objects.hash(mDisplayPowerRequest, mCurrentScreenBrightness,
+ mUserSetBrightnessChanged);
}
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
index 2907364..2b5241f 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategy.java
@@ -290,6 +290,8 @@
.setBrightnessAdjustmentFlag(mAutoBrightnessAdjustmentReasonsFlags)
.setShouldUpdateScreenBrightnessSetting(
brightness != strategyExecutionRequest.getCurrentScreenBrightness())
+ .setIsUserInitiatedChange(getAutoBrightnessAdjustmentChanged()
+ || strategyExecutionRequest.isUserSetBrightnessChanged())
.build();
}
diff --git a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
index 3463649a..0b92317 100644
--- a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
+++ b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java
@@ -45,6 +45,7 @@
// The fallback brightness might change due to clamping. Make sure we tell the rest
// of the system by updating the setting
.setShouldUpdateScreenBrightnessSetting(true)
+ .setIsUserInitiatedChange(strategyExecutionRequest.isUserSetBrightnessChanged())
.build();
}
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index f923cbc9..f56d803 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -353,7 +353,7 @@
}
/**
- * @return Whether to ignore preferredRefreshRate app request or not
+ * @return Whether to ignore preferredRefreshRate app request conversion to display mode or not
*/
public boolean ignoreAppPreferredRefreshRateRequest() {
return mIgnoreAppPreferredRefreshRate.isEnabled();
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index 76a2827..d519748 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -1254,13 +1254,9 @@
* Responsible for keeping track of app requested refresh rates per display
*/
public final class AppRequestObserver {
- private final SparseArray<Display.Mode> mAppRequestedModeByDisplay;
- private final SparseArray<RefreshRateRange> mAppPreferredRefreshRateRangeByDisplay;
private final boolean mIgnorePreferredRefreshRate;
AppRequestObserver(DisplayManagerFlags flags) {
- mAppRequestedModeByDisplay = new SparseArray<>();
- mAppPreferredRefreshRateRangeByDisplay = new SparseArray<>();
mIgnorePreferredRefreshRate = flags.ignoreAppPreferredRefreshRateRequest();
}
@@ -1269,34 +1265,77 @@
*/
public void setAppRequest(int displayId, int modeId, float requestedRefreshRate,
float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) {
+ Display.Mode requestedMode;
+ synchronized (mLock) {
+ requestedMode = findModeLocked(displayId, modeId, requestedRefreshRate);
+ }
- if (modeId == 0 && requestedRefreshRate != 0 && !mIgnorePreferredRefreshRate) {
+ Vote frameRateVote = getFrameRateVote(
+ requestedMinRefreshRateRange, requestedMaxRefreshRateRange);
+ Vote baseModeRefreshRateVote = getBaseModeVote(requestedMode, requestedRefreshRate);
+ Vote sizeVote = getSizeVote(requestedMode);
+
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
+ frameRateVote);
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+ baseModeRefreshRateVote);
+ mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
+ }
+
+ private Display.Mode findModeLocked(int displayId, int modeId, float requestedRefreshRate) {
+ Display.Mode mode = null;
+ if (modeId != 0) {
+ mode = findAppModeByIdLocked(displayId, modeId);
+ } else if (requestedRefreshRate != 0 && !mIgnorePreferredRefreshRate) { // modeId == 0
// Scan supported modes returned to find a mode with the same
// size as the default display mode but with the specified refresh rate instead.
- Display.Mode mode = findDefaultModeByRefreshRate(displayId, requestedRefreshRate);
- if (mode != null) {
- modeId = mode.getModeId();
- } else {
+ mode = findDefaultModeByRefreshRateLocked(displayId, requestedRefreshRate);
+ if (mode == null) {
Slog.e(TAG, "Couldn't find a mode for the requestedRefreshRate: "
+ requestedRefreshRate + " on Display: " + displayId);
}
}
+ return mode;
+ }
- synchronized (mLock) {
- setAppRequestedModeLocked(displayId, modeId);
- setAppPreferredRefreshRateRangeLocked(displayId, requestedMinRefreshRateRange,
- requestedMaxRefreshRateRange);
+ private Vote getFrameRateVote(float minRefreshRate, float maxRefreshRate) {
+ RefreshRateRange refreshRateRange = null;
+ if (minRefreshRate > 0 || maxRefreshRate > 0) {
+ float max = maxRefreshRate > 0
+ ? maxRefreshRate : Float.POSITIVE_INFINITY;
+ refreshRateRange = new RefreshRateRange(minRefreshRate, max);
+ if (refreshRateRange.min == 0 && refreshRateRange.max == 0) {
+ // minRefreshRate/maxRefreshRate were invalid
+ refreshRateRange = null;
+ }
}
+ return refreshRateRange != null
+ ? Vote.forRenderFrameRates(refreshRateRange.min, refreshRateRange.max) : null;
+ }
+
+ private Vote getSizeVote(@Nullable Display.Mode mode) {
+ return mode != null
+ ? Vote.forSize(mode.getPhysicalWidth(), mode.getPhysicalHeight()) : null;
+ }
+
+ private Vote getBaseModeVote(@Nullable Display.Mode mode, float requestedRefreshRate) {
+ Vote vote = null;
+ if (mode != null) {
+ if (mode.isSynthetic()) {
+ vote = Vote.forRequestedRefreshRate(mode.getRefreshRate());
+ } else {
+ vote = Vote.forBaseModeRefreshRate(mode.getRefreshRate());
+ }
+ } else if (requestedRefreshRate != 0f && mIgnorePreferredRefreshRate) {
+ vote = Vote.forRequestedRefreshRate(requestedRefreshRate);
+ } // !mIgnorePreferredRefreshRate case is handled by findModeLocked
+ return vote;
}
@Nullable
- private Display.Mode findDefaultModeByRefreshRate(int displayId, float refreshRate) {
- Display.Mode[] modes;
- Display.Mode defaultMode;
- synchronized (mLock) {
- modes = mAppSupportedModesByDisplay.get(displayId);
- defaultMode = mDefaultModeByDisplay.get(displayId);
- }
+ private Display.Mode findDefaultModeByRefreshRateLocked(int displayId, float refreshRate) {
+ Display.Mode[] modes = mAppSupportedModesByDisplay.get(displayId);
+ Display.Mode defaultMode = mDefaultModeByDisplay.get(displayId);
for (int i = 0; i < modes.length; i++) {
if (modes[i].matches(defaultMode.getPhysicalWidth(),
defaultMode.getPhysicalHeight(), refreshRate)) {
@@ -1306,69 +1345,6 @@
return null;
}
- private void setAppRequestedModeLocked(int displayId, int modeId) {
- final Display.Mode requestedMode = findAppModeByIdLocked(displayId, modeId);
- if (Objects.equals(requestedMode, mAppRequestedModeByDisplay.get(displayId))) {
- return;
- }
- final Vote baseModeRefreshRateVote;
- final Vote sizeVote;
- if (requestedMode != null) {
- mAppRequestedModeByDisplay.put(displayId, requestedMode);
- sizeVote = Vote.forSize(requestedMode.getPhysicalWidth(),
- requestedMode.getPhysicalHeight());
- if (requestedMode.isSynthetic()) {
- // TODO: for synthetic mode we should not limit frame rate, we must ensure
- // that frame rate is reachable within other Votes constraints
- baseModeRefreshRateVote = Vote.forRenderFrameRates(
- requestedMode.getRefreshRate(), requestedMode.getRefreshRate());
- } else {
- baseModeRefreshRateVote =
- Vote.forBaseModeRefreshRate(requestedMode.getRefreshRate());
- }
- } else {
- mAppRequestedModeByDisplay.remove(displayId);
- baseModeRefreshRateVote = null;
- sizeVote = null;
- }
-
- mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
- baseModeRefreshRateVote);
- mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_SIZE, sizeVote);
- }
-
- private void setAppPreferredRefreshRateRangeLocked(int displayId,
- float requestedMinRefreshRateRange, float requestedMaxRefreshRateRange) {
- final Vote vote;
-
- RefreshRateRange refreshRateRange = null;
- if (requestedMinRefreshRateRange > 0 || requestedMaxRefreshRateRange > 0) {
- float min = requestedMinRefreshRateRange;
- float max = requestedMaxRefreshRateRange > 0
- ? requestedMaxRefreshRateRange : Float.POSITIVE_INFINITY;
- refreshRateRange = new RefreshRateRange(min, max);
- if (refreshRateRange.min == 0 && refreshRateRange.max == 0) {
- // requestedMinRefreshRateRange/requestedMaxRefreshRateRange were invalid
- refreshRateRange = null;
- }
- }
-
- if (Objects.equals(refreshRateRange,
- mAppPreferredRefreshRateRangeByDisplay.get(displayId))) {
- return;
- }
-
- if (refreshRateRange != null) {
- mAppPreferredRefreshRateRangeByDisplay.put(displayId, refreshRateRange);
- vote = Vote.forRenderFrameRates(refreshRateRange.min, refreshRateRange.max);
- } else {
- mAppPreferredRefreshRateRangeByDisplay.remove(displayId);
- vote = null;
- }
- mVotesStorage.updateVote(displayId, Vote.PRIORITY_APP_REQUEST_RENDER_FRAME_RATE_RANGE,
- vote);
- }
-
private Display.Mode findAppModeByIdLocked(int displayId, int modeId) {
Display.Mode[] modes = mAppSupportedModesByDisplay.get(displayId);
if (modes == null) {
@@ -1384,19 +1360,7 @@
private void dumpLocked(PrintWriter pw) {
pw.println(" AppRequestObserver");
- pw.println(" mAppRequestedModeByDisplay:");
- for (int i = 0; i < mAppRequestedModeByDisplay.size(); i++) {
- final int id = mAppRequestedModeByDisplay.keyAt(i);
- final Display.Mode mode = mAppRequestedModeByDisplay.valueAt(i);
- pw.println(" " + id + " -> " + mode);
- }
- pw.println(" mAppPreferredRefreshRateRangeByDisplay:");
- for (int i = 0; i < mAppPreferredRefreshRateRangeByDisplay.size(); i++) {
- final int id = mAppPreferredRefreshRateRangeByDisplay.keyAt(i);
- final RefreshRateRange refreshRateRange =
- mAppPreferredRefreshRateRangeByDisplay.valueAt(i);
- pw.println(" " + id + " -> " + refreshRateRange);
- }
+ pw.println(" mIgnorePreferredRefreshRate: " + mIgnorePreferredRefreshRate);
}
}
diff --git a/services/core/java/com/android/server/display/mode/RequestedRefreshRateVote.java b/services/core/java/com/android/server/display/mode/RequestedRefreshRateVote.java
new file mode 100644
index 0000000..843cf88
--- /dev/null
+++ b/services/core/java/com/android/server/display/mode/RequestedRefreshRateVote.java
@@ -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.server.display.mode;
+
+import android.annotation.NonNull;
+
+import java.util.Objects;
+
+class RequestedRefreshRateVote implements Vote {
+ final float mRefreshRate;
+
+ RequestedRefreshRateVote(float refreshRate) {
+ this.mRefreshRate = refreshRate;
+ }
+
+ @Override
+ public void updateSummary(@NonNull VoteSummary summary) {
+ summary.requestedRefreshRates.add(mRefreshRate);
+ }
+
+ @Override
+ public String toString() {
+ return "RequestedRefreshRateVote{ refreshRate=" + mRefreshRate + " }";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof RequestedRefreshRateVote that)) return false;
+ return Float.compare(mRefreshRate, that.mRefreshRate) == 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mRefreshRate);
+ }
+}
diff --git a/services/core/java/com/android/server/display/mode/SyntheticModeManager.java b/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
index 5b6bbc5..a83b939 100644
--- a/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
+++ b/services/core/java/com/android/server/display/mode/SyntheticModeManager.java
@@ -32,7 +32,9 @@
*/
public class SyntheticModeManager {
private static final float FLOAT_TOLERANCE = 0.01f;
- private static final float SYNTHETIC_MODE_HIGH_BOUNDARY = 60f + FLOAT_TOLERANCE;
+ private static final float SYNTHETIC_MODE_REFRESH_RATE = 60f;
+ private static final float SYNTHETIC_MODE_HIGH_BOUNDARY =
+ SYNTHETIC_MODE_REFRESH_RATE + FLOAT_TOLERANCE;
private final boolean mSynthetic60HzModesEnabled;
@@ -62,7 +64,7 @@
nextModeId = mode.getModeId();
}
- float divisor = mode.getVsyncRate() / 60f;
+ float divisor = mode.getVsyncRate() / SYNTHETIC_MODE_REFRESH_RATE;
boolean is60HzAchievable = Math.abs(divisor - Math.round(divisor)) < FLOAT_TOLERANCE;
if (is60HzAchievable) {
sizes.put(new Size(mode.getPhysicalWidth(), mode.getPhysicalHeight()),
diff --git a/services/core/java/com/android/server/display/mode/Vote.java b/services/core/java/com/android/server/display/mode/Vote.java
index 3e8b3c5..1ec469c 100644
--- a/services/core/java/com/android/server/display/mode/Vote.java
+++ b/services/core/java/com/android/server/display/mode/Vote.java
@@ -186,6 +186,10 @@
return new BaseModeRefreshRateVote(baseModeRefreshRate);
}
+ static Vote forRequestedRefreshRate(float refreshRate) {
+ return new RequestedRefreshRateVote(refreshRate);
+ }
+
static Vote forSupportedRefreshRates(List<SupportedModeData> supportedModes) {
if (supportedModes.isEmpty()) {
return null;
diff --git a/services/core/java/com/android/server/display/mode/VoteSummary.java b/services/core/java/com/android/server/display/mode/VoteSummary.java
index d4ce892..00a9226 100644
--- a/services/core/java/com/android/server/display/mode/VoteSummary.java
+++ b/services/core/java/com/android/server/display/mode/VoteSummary.java
@@ -23,7 +23,9 @@
import android.view.SurfaceControl;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
+import java.util.Set;
final class VoteSummary {
private static final float FLOAT_TOLERANCE = SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE;
@@ -38,7 +40,14 @@
public int minWidth;
public int minHeight;
public boolean disableRefreshRateSwitching;
+ /**
+ * available modes should have mode with specific refresh rate
+ */
public float appRequestBaseModeRefreshRate;
+ /**
+ * requestRefreshRate should be within [minRenderFrameRate, maxRenderFrameRate] range
+ */
+ public Set<Float> requestedRefreshRates = new HashSet<>();
@Nullable
public List<SupportedRefreshRatesVote.RefreshRates> supportedRefreshRates;
@@ -329,6 +338,21 @@
}
return false;
}
+
+ for (Float requestedRefreshRate : requestedRefreshRates) {
+ if (requestedRefreshRate < minRenderFrameRate
+ || requestedRefreshRate > maxRenderFrameRate) {
+ if (mLoggingEnabled) {
+ Slog.w(TAG, "Requested refreshRate is outside frame rate range"
+ + ": requestedRefreshRates=" + requestedRefreshRates
+ + ", requestedRefreshRate=" + requestedRefreshRate
+ + ", minRenderFrameRate=" + minRenderFrameRate
+ + ", maxRenderFrameRate=" + maxRenderFrameRate);
+ }
+ return false;
+ }
+ }
+
return true;
}
@@ -370,6 +394,7 @@
minHeight = 0;
disableRefreshRateSwitching = false;
appRequestBaseModeRefreshRate = 0f;
+ requestedRefreshRates.clear();
supportedRefreshRates = null;
supportedModeIds = null;
if (mLoggingEnabled) {
@@ -393,6 +418,7 @@
+ ", minHeight=" + minHeight
+ ", disableRefreshRateSwitching=" + disableRefreshRateSwitching
+ ", appRequestBaseModeRefreshRate=" + appRequestBaseModeRefreshRate
+ + ", requestRefreshRates=" + requestedRefreshRates
+ ", supportedRefreshRates=" + supportedRefreshRates
+ ", supportedModeIds=" + supportedModeIds
+ ", mIsDisplayResolutionRangeVotingEnabled="
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index dbd1e65..6e027c6 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1029,6 +1029,10 @@
/** Helper method for sending feature discovery command */
private void reportFeatures(boolean isTvDeviceSetting) {
+ // <Report Features> should only be sent for HDMI 2.0
+ if (getCecVersion() < HdmiControlManager.HDMI_CEC_VERSION_2_0) {
+ return;
+ }
// check if tv device is enabled for tv device specific RC profile setting
if (isTvDeviceSetting) {
if (isTvDeviceEnabled()) {
diff --git a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
index dd6433d..82ecb4a 100644
--- a/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
+++ b/services/core/java/com/android/server/inputmethod/AdditionalSubtypeMapRepository.java
@@ -16,12 +16,16 @@
package com.android.server.inputmethod;
+import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
+import android.annotation.WorkerThread;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.Handler;
+import android.os.Process;
+import android.util.IntArray;
import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
@@ -29,6 +33,10 @@
import com.android.server.LocalServices;
import com.android.server.pm.UserManagerInternal;
+import java.util.ArrayList;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.ReentrantLock;
+
/**
* Provides accesses to per-user additional {@link android.view.inputmethod.InputMethodSubtype}
* persistent storages.
@@ -38,6 +46,152 @@
@NonNull
private static final SparseArray<AdditionalSubtypeMap> sPerUserMap = new SparseArray<>();
+ record WriteTask(@UserIdInt int userId, @NonNull AdditionalSubtypeMap subtypeMap,
+ @NonNull InputMethodMap inputMethodMap) {
+ }
+
+ static final class SingleThreadedBackgroundWriter {
+ /**
+ * A {@link ReentrantLock} used to guard {@link #mPendingTasks} and {@link #mRemovedUsers}.
+ */
+ @NonNull
+ private final ReentrantLock mLock = new ReentrantLock();
+ /**
+ * A {@link Condition} associated with {@link #mLock} for producer to unblock consumer.
+ */
+ @NonNull
+ private final Condition mLockNotifier = mLock.newCondition();
+
+ @GuardedBy("mLock")
+ @NonNull
+ private final SparseArray<WriteTask> mPendingTasks = new SparseArray<>();
+
+ @GuardedBy("mLock")
+ private final IntArray mRemovedUsers = new IntArray();
+
+ @NonNull
+ private final Thread mWriterThread = new Thread("android.ime.as") {
+
+ /**
+ * Waits until the next data has come then return the result after filtering out any
+ * already removed users.
+ *
+ * @return A list of {@link WriteTask} to be written into persistent storage
+ */
+ @WorkerThread
+ private ArrayList<WriteTask> fetchNextTasks() {
+ final SparseArray<WriteTask> tasks;
+ final IntArray removedUsers;
+ mLock.lock();
+ try {
+ while (true) {
+ if (mPendingTasks.size() != 0) {
+ tasks = mPendingTasks.clone();
+ mPendingTasks.clear();
+ if (mRemovedUsers.size() == 0) {
+ removedUsers = null;
+ } else {
+ removedUsers = mRemovedUsers.clone();
+ }
+ break;
+ }
+ mLockNotifier.awaitUninterruptibly();
+ }
+ } finally {
+ mLock.unlock();
+ }
+ final int size = tasks.size();
+ final ArrayList<WriteTask> result = new ArrayList<>(size);
+ for (int i = 0; i < size; ++i) {
+ final int userId = tasks.keyAt(i);
+ if (removedUsers != null && removedUsers.contains(userId)) {
+ continue;
+ }
+ result.add(tasks.valueAt(i));
+ }
+ return result;
+ }
+
+ @WorkerThread
+ @Override
+ public void run() {
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+ while (true) {
+ final ArrayList<WriteTask> tasks = fetchNextTasks();
+ tasks.forEach(task -> AdditionalSubtypeUtils.save(
+ task.subtypeMap, task.inputMethodMap, task.userId));
+ }
+ }
+ };
+
+ /**
+ * Schedules a write operation
+ *
+ * @param userId the target user ID of this operation
+ * @param subtypeMap {@link AdditionalSubtypeMap} to be saved
+ * @param inputMethodMap {@link InputMethodMap} to be used to filter our {@code subtypeMap}
+ */
+ @AnyThread
+ void scheduleWriteTask(@UserIdInt int userId, @NonNull AdditionalSubtypeMap subtypeMap,
+ @NonNull InputMethodMap inputMethodMap) {
+ final var task = new WriteTask(userId, subtypeMap, inputMethodMap);
+ mLock.lock();
+ try {
+ if (mRemovedUsers.contains(userId)) {
+ return;
+ }
+ mPendingTasks.put(userId, task);
+ mLockNotifier.signalAll();
+ } finally {
+ mLock.unlock();
+ }
+ }
+
+ /**
+ * Called back when a user is being created.
+ *
+ * @param userId The user ID to be created
+ */
+ @AnyThread
+ void onUserCreated(@UserIdInt int userId) {
+ mLock.lock();
+ try {
+ for (int i = mRemovedUsers.size() - 1; i >= 0; --i) {
+ if (mRemovedUsers.get(i) == userId) {
+ mRemovedUsers.remove(i);
+ }
+ }
+ } finally {
+ mLock.unlock();
+ }
+ }
+
+ /**
+ * Called back when a user is being removed. Any pending task will be effectively canceled
+ * if the user is removed before the task is fulfilled.
+ *
+ * @param userId The user ID to be removed
+ */
+ @AnyThread
+ void onUserRemoved(@UserIdInt int userId) {
+ mLock.lock();
+ try {
+ mRemovedUsers.add(userId);
+ mPendingTasks.remove(userId);
+ } finally {
+ mLock.unlock();
+ }
+ }
+
+ void startThread() {
+ mWriterThread.start();
+ }
+ }
+
+ private static final SingleThreadedBackgroundWriter sWriter =
+ new SingleThreadedBackgroundWriter();
+
/**
* Not intended to be instantiated.
*/
@@ -64,9 +218,11 @@
return;
}
sPerUserMap.put(userId, map);
- // TODO: Offload this to a background thread.
- // TODO: Skip if the previous data is exactly the same as new one.
- AdditionalSubtypeUtils.save(map, inputMethodMap, userId);
+ sWriter.scheduleWriteTask(userId, map, inputMethodMap);
+ }
+
+ static void startWriterThread() {
+ sWriter.startThread();
}
static void initialize(@NonNull Handler handler, @NonNull Context context) {
@@ -78,6 +234,7 @@
@Override
public void onUserCreated(UserInfo user, @Nullable Object token) {
final int userId = user.id;
+ sWriter.onUserCreated(userId);
handler.post(() -> {
synchronized (ImfLock.class) {
if (!sPerUserMap.contains(userId)) {
@@ -99,6 +256,7 @@
@Override
public void onUserRemoved(UserInfo user) {
final int userId = user.id;
+ sWriter.onUserRemoved(userId);
handler.post(() -> {
synchronized (ImfLock.class) {
sPerUserMap.remove(userId);
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 79f1a9c..c19cb03 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -55,7 +55,6 @@
import java.util.List;
import java.util.Objects;
import java.util.OptionalInt;
-import java.util.function.IntConsumer;
// TODO(b/210039666): See if we can make this class thread-safe.
final class HandwritingModeController {
@@ -91,7 +90,6 @@
private boolean mDelegationConnectionlessFlow;
private Runnable mDelegationIdleTimeoutRunnable;
private Handler mDelegationIdleTimeoutHandler;
- private IntConsumer mPointerToolTypeConsumer;
private final Runnable mDiscardDelegationTextRunnable;
private HandwritingEventReceiverSurface mHandwritingSurface;
@@ -99,7 +97,7 @@
@AnyThread
HandwritingModeController(Context context, Looper uiThreadLooper,
- Runnable inkWindowInitRunnable, IntConsumer toolTypeConsumer,
+ Runnable inkWindowInitRunnable,
Runnable discardDelegationTextRunnable) {
mContext = context;
mLooper = uiThreadLooper;
@@ -109,7 +107,6 @@
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mCurrentRequestId = 0;
mInkWindowInitRunnable = inkWindowInitRunnable;
- mPointerToolTypeConsumer = toolTypeConsumer;
mDiscardDelegationTextRunnable = discardDelegationTextRunnable;
}
@@ -389,16 +386,10 @@
"Input Event should not be processed when IME has the spy channel.");
}
- if (!(ev instanceof MotionEvent)) {
+ if (!(ev instanceof MotionEvent event)) {
Slog.wtf(TAG, "Received non-motion event in stylus monitor.");
return false;
}
- final MotionEvent event = (MotionEvent) ev;
- if (mPointerToolTypeConsumer != null && event.getAction() == MotionEvent.ACTION_DOWN) {
- int toolType = event.getToolType(event.getActionIndex());
- // notify IME of change in tool type.
- mPointerToolTypeConsumer.accept(toolType);
- }
if (!event.isStylusPointer()) {
return false;
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index dace67f..f61ca61 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -16,6 +16,8 @@
package com.android.server.inputmethod;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -30,6 +32,9 @@
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.server.LocalServices;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
import java.util.Collections;
import java.util.List;
@@ -38,6 +43,16 @@
*/
public abstract class InputMethodManagerInternal {
/**
+ * Indicates that the method is guaranteed to not require {@link ImfLock}.
+ *
+ * <p>You can call this method without worrying about system_server lock layering.</p>
+ */
+ @Retention(SOURCE)
+ @Target({ElementType.METHOD})
+ public @interface ImfLockFree {
+ }
+
+ /**
* Listener for input method list changed events.
*/
public interface InputMethodListListener {
@@ -53,6 +68,7 @@
*
* @param interactive the interactive mode parameter
*/
+ @ImfLockFree
public abstract void setInteractive(boolean interactive);
/**
@@ -61,6 +77,7 @@
* @param reason the reason for hiding the current input method
* @param originatingDisplayId the display ID the request is originated
*/
+ @ImfLockFree
public abstract void hideAllInputMethods(@SoftInputShowHideReason int reason,
int originatingDisplayId);
@@ -143,6 +160,7 @@
*
* @param listener the listener to add
*/
+ @ImfLockFree
public abstract void registerInputMethodListListener(InputMethodListListener listener);
/**
@@ -178,6 +196,7 @@
*
* @param displayId the display hosting the IME window
*/
+ @ImfLockFree
public abstract void removeImeSurface(int displayId);
/**
@@ -188,12 +207,14 @@
* @param disableImeIcon indicates whether IME icon should be enabled or not
* @param displayId the display for which to update the IME window status
*/
+ @ImfLockFree
public abstract void updateImeWindowStatus(boolean disableImeIcon, int displayId);
/**
* Finish stylus handwriting by calling {@link InputMethodService#finishStylusHandwriting()} if
* there is an ongoing handwriting session.
*/
+ @ImfLockFree
public abstract void maybeFinishStylusHandwriting();
/**
@@ -240,10 +261,12 @@
*/
private static final InputMethodManagerInternal NOP =
new InputMethodManagerInternal() {
+ @ImfLockFree
@Override
public void setInteractive(boolean interactive) {
}
+ @ImfLockFree
@Override
public void hideAllInputMethods(@SoftInputShowHideReason int reason,
int originatingDisplayId) {
@@ -282,6 +305,7 @@
int deviceId, @Nullable String imeId) {
}
+ @ImfLockFree
@Override
public void registerInputMethodListListener(InputMethodListListener listener) {
}
@@ -300,10 +324,12 @@
public void onImeParentChanged(int displayId) {
}
+ @ImfLockFree
@Override
public void removeImeSurface(int displayId) {
}
+ @ImfLockFree
@Override
public void updateImeWindowStatus(boolean disableImeIcon, int displayId) {
}
@@ -318,6 +344,7 @@
@UserIdInt int userId) {
}
+ @ImfLockFree
@Override
public void maybeFinishStylusHandwriting() {
}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 5843d72..d22438f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -205,7 +205,6 @@
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
-import java.util.function.IntConsumer;
import java.util.function.IntFunction;
/**
@@ -1305,12 +1304,9 @@
com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
mNonPreemptibleInputMethods = mRes.getStringArray(
com.android.internal.R.array.config_nonPreemptibleInputMethods);
- IntConsumer toolTypeConsumer =
- Flags.useHandwritingListenerForTooltype()
- ? toolType -> onUpdateEditorToolType(toolType) : null;
Runnable discardDelegationTextRunnable = () -> discardHandwritingDelegationText();
mHwController = new HandwritingModeController(mContext, thread.getLooper(),
- new InkWindowInitializer(), toolTypeConsumer, discardDelegationTextRunnable);
+ new InkWindowInitializer(), discardDelegationTextRunnable);
registerDeviceListenerAndCheckStylusSupport();
}
}
@@ -1547,6 +1543,10 @@
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
getPackageManagerForUser(mContext, currentUserId),
newSettings.getEnabledInputMethodList());
+
+ final var unused = SystemServerInitThreadPool.submit(
+ AdditionalSubtypeMapRepository::startWriterThread,
+ "Start AdditionalSubtypeMapRepository's writer thread");
}
}
}
@@ -3412,8 +3412,10 @@
ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
mCurStatsToken = null;
- if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
- curMethod.updateEditorToolType(lastClickToolType);
+ if (Flags.useHandwritingListenerForTooltype()) {
+ maybeReportToolType();
+ } else if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
+ onUpdateEditorToolType(lastClickToolType);
}
mVisibilityApplier.performShowIme(windowToken, statsToken,
mVisibilityStateComputer.getShowFlagsForInputMethodServiceOnly(),
@@ -3427,6 +3429,29 @@
return false;
}
+ @GuardedBy("ImfLock.class")
+ private void maybeReportToolType() {
+ int lastDeviceId = mInputManagerInternal.getLastUsedInputDeviceId();
+ final InputManager im = mContext.getSystemService(InputManager.class);
+ if (im == null) {
+ return;
+ }
+ InputDevice device = im.getInputDevice(lastDeviceId);
+ if (device == null) {
+ return;
+ }
+ int toolType;
+ if (isStylusDevice(device)) {
+ toolType = MotionEvent.TOOL_TYPE_STYLUS;
+ } else if (isFingerDevice(device)) {
+ toolType = MotionEvent.TOOL_TYPE_FINGER;
+ } else {
+ // other toolTypes are irrelevant and reported as unknown.
+ toolType = MotionEvent.TOOL_TYPE_UNKNOWN;
+ }
+ onUpdateEditorToolType(toolType);
+ }
+
@Override
public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken,
@NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags,
@@ -4281,6 +4306,10 @@
|| inputDevice.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS);
}
+ private static boolean isFingerDevice(InputDevice inputDevice) {
+ return inputDevice.supportsSource(InputDevice.SOURCE_TOUCHSCREEN);
+ }
+
@GuardedBy("ImfLock.class")
private boolean hasSupportedStylusLocked() {
return mStylusIds != null && mStylusIds.size() != 0;
@@ -5471,12 +5500,14 @@
private final class LocalServiceImpl extends InputMethodManagerInternal {
+ @ImfLockFree
@Override
public void setInteractive(boolean interactive) {
// Do everything in handler so as not to block the caller.
mHandler.obtainMessage(MSG_SET_INTERACTIVE, interactive ? 1 : 0, 0).sendToTarget();
}
+ @ImfLockFree
@Override
public void hideAllInputMethods(@SoftInputShowHideReason int reason,
int originatingDisplayId) {
@@ -5562,6 +5593,7 @@
}
}
+ @ImfLockFree
@Override
public void registerInputMethodListListener(InputMethodListListener listener) {
mInputMethodListListeners.addIfAbsent(listener);
@@ -5609,11 +5641,13 @@
}
}
+ @ImfLockFree
@Override
public void removeImeSurface(int displayId) {
mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget();
}
+ @ImfLockFree
@Override
public void updateImeWindowStatus(boolean disableImeIcon, int displayId) {
mHandler.obtainMessage(MSG_UPDATE_IME_WINDOW_STATUS, disableImeIcon ? 1 : 0, 0)
@@ -5691,6 +5725,7 @@
}
}
+ @ImfLockFree
@Override
public void maybeFinishStylusHandwriting() {
mHandler.removeMessages(MSG_FINISH_HANDWRITING);
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
index 1c958a9..23f947c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java
@@ -367,9 +367,9 @@
}
protected void dump(final Printer pw, final String prefix) {
- for (int i = 0; i < mUsageHistoryOfSubtypeListItemIndex.length; ++i) {
- final int rank = mUsageHistoryOfSubtypeListItemIndex[i];
- final ImeSubtypeListItem item = mImeSubtypeList.get(i);
+ for (int rank = 0; rank < mUsageHistoryOfSubtypeListItemIndex.length; ++rank) {
+ final int index = mUsageHistoryOfSubtypeListItemIndex[rank];
+ final ImeSubtypeListItem item = mImeSubtypeList.get(index);
pw.println(prefix + "rank=" + rank + " item=" + item);
}
}
diff --git a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
index 563f93e..b9e0960 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerBackupHelper.java
@@ -84,9 +84,16 @@
* from the delegate selector.
*/
private static final String LOCALES_FROM_DELEGATE_PREFS = "LocalesFromDelegatePrefs.xml";
+ private static final String LOCALES_STAGED_DATA_PREFS = "LocalesStagedDataPrefs.xml";
+ private static final String ARCHIVED_PACKAGES_PREFS = "ArchivedPackagesPrefs.xml";
// Stage data would be deleted on reboot since it's stored in memory. So it's retained until
// retention period OR next reboot, whichever happens earlier.
private static final Duration STAGE_DATA_RETENTION_PERIOD = Duration.ofDays(3);
+ // Store the locales staged data for the specified package in the SharedPreferences. The format
+ // is locales s:setFromDelegate
+ // For example: en-US s:true
+ private static final String STRING_SPLIT = " s:";
+ private static final String KEY_STAGED_DATA_TIME = "staged_data_time";
private final LocaleManagerService mLocaleManagerService;
private final PackageManager mPackageManager;
@@ -94,39 +101,34 @@
private final Context mContext;
private final Object mStagedDataLock = new Object();
- // Staged data map keyed by user-id to handle multi-user scenario / work profiles. We are using
- // SparseArray because it is more memory-efficient than a HashMap.
- private final SparseArray<StagedData> mStagedData;
-
// SharedPreferences to store packages whose app-locale was set by a delegate, as opposed to
// the application setting the app-locale itself.
private final SharedPreferences mDelegateAppLocalePackages;
+ // For unit tests
+ private final SparseArray<File> mStagedDataFiles;
+ private final File mArchivedPackagesFile;
+
private final BroadcastReceiver mUserMonitor;
- // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon receiving
- // the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform the data
- // restoration during the second PACKAGE_ADDED broadcast, which is sent subsequently when the
- // app is installed.
- private final Set<String> mPkgsToRestore;
LocaleManagerBackupHelper(LocaleManagerService localeManagerService,
PackageManager packageManager, HandlerThread broadcastHandlerThread) {
this(localeManagerService.mContext, localeManagerService, packageManager, Clock.systemUTC(),
- new SparseArray<>(), broadcastHandlerThread, null);
+ broadcastHandlerThread, null, null, null);
}
- @VisibleForTesting LocaleManagerBackupHelper(Context context,
- LocaleManagerService localeManagerService,
- PackageManager packageManager, Clock clock, SparseArray<StagedData> stagedData,
- HandlerThread broadcastHandlerThread, SharedPreferences delegateAppLocalePackages) {
+ @VisibleForTesting
+ LocaleManagerBackupHelper(Context context, LocaleManagerService localeManagerService,
+ PackageManager packageManager, Clock clock, HandlerThread broadcastHandlerThread,
+ SparseArray<File> stagedDataFiles, File archivedPackagesFile,
+ SharedPreferences delegateAppLocalePackages) {
mContext = context;
mLocaleManagerService = localeManagerService;
mPackageManager = packageManager;
mClock = clock;
- mStagedData = stagedData;
mDelegateAppLocalePackages = delegateAppLocalePackages != null ? delegateAppLocalePackages
- : createPersistedInfo();
- mPkgsToRestore = new ArraySet<>();
-
+ : createPersistedInfo();
+ mArchivedPackagesFile = archivedPackagesFile;
+ mStagedDataFiles = stagedDataFiles;
mUserMonitor = new UserMonitor();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_USER_REMOVED);
@@ -148,7 +150,7 @@
}
synchronized (mStagedDataLock) {
- cleanStagedDataForOldEntriesLocked();
+ cleanStagedDataForOldEntriesLocked(userId);
}
HashMap<String, LocalesInfo> pkgStates = new HashMap<>();
@@ -207,14 +209,11 @@
return out.toByteArray();
}
- private void cleanStagedDataForOldEntriesLocked() {
- for (int i = 0; i < mStagedData.size(); i++) {
- int userId = mStagedData.keyAt(i);
- StagedData stagedData = mStagedData.get(userId);
- if (stagedData.mCreationTimeMillis
- < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) {
- deleteStagedDataLocked(userId);
- }
+ private void cleanStagedDataForOldEntriesLocked(@UserIdInt int userId) {
+ Long created_time = getStagedDataSp(userId).getLong(KEY_STAGED_DATA_TIME, -1);
+ if (created_time != -1
+ && created_time < mClock.millis() - STAGE_DATA_RETENTION_PERIOD.toMillis()) {
+ deleteStagedDataLocked(userId);
}
}
@@ -252,20 +251,16 @@
// performed simultaneously.
synchronized (mStagedDataLock) {
// Backups for apps which are yet to be installed.
- StagedData stagedData = new StagedData(mClock.millis(), new HashMap<>());
-
for (String pkgName : pkgStates.keySet()) {
LocalesInfo localesInfo = pkgStates.get(pkgName);
// Check if the application is already installed for the concerned user.
if (isPackageInstalledForUser(pkgName, userId)) {
- if (mPkgsToRestore != null) {
- mPkgsToRestore.remove(pkgName);
- }
+ removeFromArchivedPackagesInfo(userId, pkgName);
// Don't apply the restore if the locales have already been set for the app.
checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId);
} else {
// Stage the data if the app isn't installed.
- stagedData.mPackageStates.put(pkgName, localesInfo);
+ storeStagedDataInfo(userId, pkgName, localesInfo);
if (DEBUG) {
Slog.d(TAG, "Add locales=" + localesInfo.mLocales
+ " fromDelegate=" + localesInfo.mSetFromDelegate
@@ -274,8 +269,9 @@
}
}
- if (!stagedData.mPackageStates.isEmpty()) {
- mStagedData.put(userId, stagedData);
+ // Create the time if the data is being staged.
+ if (!getStagedDataSp(userId).getAll().isEmpty()) {
+ storeStagedDataCreatedTime(userId);
}
}
}
@@ -293,14 +289,23 @@
* added on device.
*/
void onPackageAddedWithExtras(String packageName, int uid, Bundle extras) {
- boolean archived = false;
+ int userId = UserHandle.getUserId(uid);
if (extras != null) {
- archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false);
- if (archived && mPkgsToRestore != null) {
- mPkgsToRestore.add(packageName);
+ // To determine whether an app is pre-archived, check for Intent.EXTRA_ARCHIVAL upon
+ // receiving the initial PACKAGE_ADDED broadcast. If it is indeed pre-archived, perform
+ // the data restoration during the second PACKAGE_ADDED broadcast, which is sent
+ // subsequently when the app is installed.
+ boolean archived = extras.getBoolean(Intent.EXTRA_ARCHIVAL, false);
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onPackageAddedWithExtras packageName: " + packageName + ", userId: "
+ + userId + ", archived: " + archived);
+ }
+ if (archived) {
+ addInArchivedPackagesInfo(userId, packageName);
}
}
- checkStageDataAndApplyRestore(packageName, uid);
+ checkStageDataAndApplyRestore(packageName, userId);
}
/**
@@ -310,9 +315,32 @@
*/
void onPackageUpdateFinished(String packageName, int uid) {
int userId = UserHandle.getUserId(uid);
- if (mPkgsToRestore != null && mPkgsToRestore.contains(packageName)) {
- mPkgsToRestore.remove(packageName);
- checkStageDataAndApplyRestore(packageName, uid);
+ if (DEBUG) {
+ Slog.d(TAG,
+ "onPackageUpdateFinished userId: " + userId + ", packageName: " + packageName);
+ }
+ String user = Integer.toString(userId);
+ File file = getArchivedPackagesFile();
+ if (file.exists()) {
+ SharedPreferences sp = getArchivedPackagesSp(file);
+ Set<String> packageNames = new ArraySet<>(sp.getStringSet(user, new ArraySet<>()));
+ if (packageNames.remove(packageName)) {
+ SharedPreferences.Editor editor = sp.edit();
+ if (packageNames.isEmpty()) {
+ if (!editor.remove(user).commit()) {
+ Slog.e(TAG, "Failed to remove the user");
+ }
+ if (sp.getAll().isEmpty()) {
+ file.delete();
+ }
+ } else {
+ // commit and log the result.
+ if (!editor.putStringSet(user, packageNames).commit()) {
+ Slog.e(TAG, "failed to remove the package");
+ }
+ }
+ checkStageDataAndApplyRestore(packageName, userId);
+ }
}
cleanApplicationLocalesIfNeeded(packageName, userId);
}
@@ -347,16 +375,16 @@
}
}
- private void checkStageDataAndApplyRestore(String packageName, int uid) {
+ private void checkStageDataAndApplyRestore(String packageName, int userId) {
try {
synchronized (mStagedDataLock) {
- cleanStagedDataForOldEntriesLocked();
-
- int userId = UserHandle.getUserId(uid);
- if (mStagedData.contains(userId)) {
- if (mPkgsToRestore != null) {
- mPkgsToRestore.remove(packageName);
+ cleanStagedDataForOldEntriesLocked(userId);
+ if (!getStagedDataSp(userId).getString(packageName, "").isEmpty()) {
+ if (DEBUG) {
+ Slog.d(TAG,
+ "checkStageDataAndApplyRestore, remove package and restore data");
}
+ removeFromArchivedPackagesInfo(userId, packageName);
// Perform lazy restore only if the staged data exists.
doLazyRestoreLocked(packageName, userId);
}
@@ -417,8 +445,17 @@
}
}
- private void deleteStagedDataLocked(@UserIdInt int userId) {
- mStagedData.remove(userId);
+ void deleteStagedDataLocked(@UserIdInt int userId) {
+ File stagedFile = getStagedDataFile(userId);
+ SharedPreferences sp = getStagedDataSp(stagedFile);
+ // commit and log the result.
+ if (!sp.edit().clear().commit()) {
+ Slog.e(TAG, "Failed to commit data!");
+ }
+
+ if (stagedFile.exists()) {
+ stagedFile.delete();
+ }
}
/**
@@ -473,16 +510,6 @@
out.endDocument();
}
- static class StagedData {
- final long mCreationTimeMillis;
- final HashMap<String, LocalesInfo> mPackageStates;
-
- StagedData(long creationTimeMillis, HashMap<String, LocalesInfo> pkgStates) {
- mCreationTimeMillis = creationTimeMillis;
- mPackageStates = pkgStates;
- }
- }
-
static class LocalesInfo {
final String mLocales;
final boolean mSetFromDelegate;
@@ -508,6 +535,7 @@
synchronized (mStagedDataLock) {
deleteStagedDataLocked(userId);
removeProfileFromPersistedInfo(userId);
+ removeArchivedPackagesForUser(userId);
}
}
} catch (Exception e) {
@@ -533,26 +561,159 @@
return;
}
- StagedData stagedData = mStagedData.get(userId);
- for (String pkgName : stagedData.mPackageStates.keySet()) {
- LocalesInfo localesInfo = stagedData.mPackageStates.get(pkgName);
-
- if (pkgName.equals(packageName)) {
-
- checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId);
-
- // Remove the restored entry from the staged data list.
- stagedData.mPackageStates.remove(pkgName);
-
- // Remove the stage data entry for user if there are no more packages to restore.
- if (stagedData.mPackageStates.isEmpty()) {
- mStagedData.remove(userId);
- }
-
- // No need to loop further after restoring locales because the staged data will
- // contain at most one entry for the newly added package.
- break;
+ SharedPreferences sp = getStagedDataSp(userId);
+ String value = sp.getString(packageName, "");
+ if (!value.isEmpty()) {
+ String[] info = value.split(STRING_SPLIT);
+ if (info == null || info.length != 2) {
+ Slog.e(TAG, "Failed to restore data");
+ return;
}
+ LocalesInfo localesInfo = new LocalesInfo(info[0], Boolean.parseBoolean(info[1]));
+ checkExistingLocalesAndApplyRestore(packageName, localesInfo, userId);
+
+ // Remove the restored entry from the staged data list.
+ if (!sp.edit().remove(packageName).commit()) {
+ Slog.e(TAG, "Failed to commit data!");
+ }
+ }
+
+ // Remove the stage data entry for user if there are no more packages to restore.
+ if (sp.getAll().size() == 1 && sp.getLong(KEY_STAGED_DATA_TIME, -1) != -1) {
+ deleteStagedDataLocked(userId);
+ }
+ }
+
+ private File getStagedDataFile(@UserIdInt int userId) {
+ return mStagedDataFiles == null ? new File(Environment.getDataSystemDeDirectory(userId),
+ LOCALES_STAGED_DATA_PREFS) : mStagedDataFiles.get(userId);
+ }
+
+ private SharedPreferences getStagedDataSp(File file) {
+ return mStagedDataFiles == null ? mContext.createDeviceProtectedStorageContext()
+ .getSharedPreferences(file, Context.MODE_PRIVATE)
+ : mContext.getSharedPreferences(file, Context.MODE_PRIVATE);
+ }
+
+ private SharedPreferences getStagedDataSp(@UserIdInt int userId) {
+ return mStagedDataFiles == null ? mContext.createDeviceProtectedStorageContext()
+ .getSharedPreferences(getStagedDataFile(userId), Context.MODE_PRIVATE)
+ : mContext.getSharedPreferences(mStagedDataFiles.get(userId), Context.MODE_PRIVATE);
+ }
+
+ /**
+ * Store the staged locales info.
+ */
+ private void storeStagedDataInfo(@UserIdInt int userId, @NonNull String packageName,
+ @NonNull LocalesInfo localesInfo) {
+ if (DEBUG) {
+ Slog.d(TAG, "storeStagedDataInfo, userId: " + userId + ", packageName: " + packageName
+ + ", localesInfo.mLocales: " + localesInfo.mLocales
+ + ", localesInfo.mSetFromDelegate: " + localesInfo.mSetFromDelegate);
+ }
+ String info =
+ localesInfo.mLocales + STRING_SPLIT + String.valueOf(localesInfo.mSetFromDelegate);
+ SharedPreferences sp = getStagedDataSp(userId);
+ // commit and log the result.
+ if (!sp.edit().putString(packageName, info).commit()) {
+ Slog.e(TAG, "Failed to commit data!");
+ }
+ }
+
+ /**
+ * Store the time of creation for staged locales info.
+ */
+ private void storeStagedDataCreatedTime(@UserIdInt int userId) {
+ SharedPreferences sp = getStagedDataSp(userId);
+ // commit and log the result.
+ if (!sp.edit().putLong(KEY_STAGED_DATA_TIME, mClock.millis()).commit()) {
+ Slog.e(TAG, "Failed to commit data!");
+ }
+ }
+
+ private File getArchivedPackagesFile() {
+ return mArchivedPackagesFile == null ? new File(
+ Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM),
+ ARCHIVED_PACKAGES_PREFS) : mArchivedPackagesFile;
+ }
+
+ private SharedPreferences getArchivedPackagesSp(File file) {
+ return mArchivedPackagesFile == null ? mContext.createDeviceProtectedStorageContext()
+ .getSharedPreferences(file, Context.MODE_PRIVATE)
+ : mContext.getSharedPreferences(file, Context.MODE_PRIVATE);
+ }
+
+ /**
+ * Add the package into the archived packages list.
+ */
+ private void addInArchivedPackagesInfo(@UserIdInt int userId, @NonNull String packageName) {
+ String user = Integer.toString(userId);
+ SharedPreferences sp = getArchivedPackagesSp(getArchivedPackagesFile());
+ Set<String> packageNames = new ArraySet<>(sp.getStringSet(user, new ArraySet<>()));
+ if (DEBUG) {
+ Slog.d(TAG, "addInArchivedPackagesInfo before packageNames: " + packageNames
+ + ", packageName: " + packageName);
+ }
+ if (packageNames.add(packageName)) {
+ // commit and log the result.
+ if (!sp.edit().putStringSet(user, packageNames).commit()) {
+ Slog.e(TAG, "failed to add the package");
+ }
+ }
+ }
+
+ /**
+ * Remove the package from the archived packages list.
+ */
+ private void removeFromArchivedPackagesInfo(@UserIdInt int userId,
+ @NonNull String packageName) {
+ File file = getArchivedPackagesFile();
+ if (file.exists()) {
+ String user = Integer.toString(userId);
+ SharedPreferences sp = getArchivedPackagesSp(getArchivedPackagesFile());
+ Set<String> packageNames = new ArraySet<>(sp.getStringSet(user, new ArraySet<>()));
+ if (DEBUG) {
+ Slog.d(TAG, "removeFromArchivedPackagesInfo before packageNames: " + packageNames
+ + ", packageName: " + packageName);
+ }
+ if (packageNames.remove(packageName)) {
+ SharedPreferences.Editor editor = sp.edit();
+ if (packageNames.isEmpty()) {
+ if (!editor.remove(user).commit()) {
+ Slog.e(TAG, "Failed to remove user");
+ }
+ if (sp.getAll().isEmpty()) {
+ file.delete();
+ }
+ } else {
+ // commit and log the result.
+ if (!editor.putStringSet(user, packageNames).commit()) {
+ Slog.e(TAG, "failed to remove the package");
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove the user from the archived packages list.
+ */
+ private void removeArchivedPackagesForUser(@UserIdInt int userId) {
+ String user = Integer.toString(userId);
+ File file = getArchivedPackagesFile();
+ SharedPreferences sp = getArchivedPackagesSp(file);
+
+ if (sp == null || !sp.contains(user)) {
+ Slog.w(TAG, "The profile is not existed in the archived package info");
+ return;
+ }
+
+ if (!sp.edit().remove(user).commit()) {
+ Slog.e(TAG, "Failed to remove user");
+ }
+
+ if (sp.getAll().isEmpty() && file.exists()) {
+ file.delete();
}
}
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index b3ab229..ae3d36a 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -593,7 +593,7 @@
public RebootEscrowManager getRebootEscrowManager(RebootEscrowManager.Callbacks callbacks,
LockSettingsStorage storage) {
return new RebootEscrowManager(mContext, callbacks, storage,
- getHandler(getServiceThread()));
+ getHandler(getServiceThread()), getUserManagerInternal());
}
public int binderGetCallingUid() {
diff --git a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
index e1cd2c5..d0b8990 100644
--- a/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
+++ b/services/core/java/com/android/server/locksettings/RebootEscrowManager.java
@@ -51,16 +51,20 @@
import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.widget.RebootEscrowListener;
+import com.android.server.pm.UserManagerInternal;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Date;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
+import java.util.Set;
import javax.crypto.SecretKey;
@@ -138,6 +142,7 @@
ERROR_KEYSTORE_FAILURE,
ERROR_NO_NETWORK,
ERROR_TIMEOUT_EXHAUSTED,
+ ERROR_NO_REBOOT_ESCROW_DATA,
})
@Retention(RetentionPolicy.SOURCE)
@interface RebootEscrowErrorCode {
@@ -153,6 +158,7 @@
static final int ERROR_KEYSTORE_FAILURE = 7;
static final int ERROR_NO_NETWORK = 8;
static final int ERROR_TIMEOUT_EXHAUSTED = 9;
+ static final int ERROR_NO_REBOOT_ESCROW_DATA = 10;
private @RebootEscrowErrorCode int mLoadEscrowDataErrorCode = ERROR_NONE;
@@ -222,11 +228,16 @@
private final RebootEscrowKeyStoreManager mKeyStoreManager;
private final LockSettingsStorage mStorage;
private RebootEscrowProviderInterface mRebootEscrowProvider;
+ private final UserManagerInternal mUserManagerInternal;
- Injector(Context context, LockSettingsStorage storage) {
+ Injector(
+ Context context,
+ LockSettingsStorage storage,
+ UserManagerInternal userManagerInternal) {
mContext = context;
mStorage = storage;
mKeyStoreManager = new RebootEscrowKeyStoreManager();
+ mUserManagerInternal = userManagerInternal;
}
private RebootEscrowProviderInterface createRebootEscrowProvider() {
@@ -326,6 +337,10 @@
return (UserManager) mContext.getSystemService(Context.USER_SERVICE);
}
+ public UserManagerInternal getUserManagerInternal() {
+ return mUserManagerInternal;
+ }
+
public RebootEscrowKeyStoreManager getKeyStoreManager() {
return mKeyStoreManager;
}
@@ -402,8 +417,8 @@
}
RebootEscrowManager(Context context, Callbacks callbacks, LockSettingsStorage storage,
- Handler handler) {
- this(new Injector(context, storage), callbacks, storage, handler);
+ Handler handler, UserManagerInternal userManagerInternal) {
+ this(new Injector(context, storage, userManagerInternal), callbacks, storage, handler);
}
@VisibleForTesting
@@ -451,18 +466,50 @@
onEscrowRestoreComplete(false, attemptCount, retryHandler);
}
- void loadRebootEscrowDataIfAvailable(Handler retryHandler) {
- List<UserInfo> users = mUserManager.getUsers();
- List<UserInfo> rebootEscrowUsers = new ArrayList<>();
+ private List<UserInfo> getUsersToUnlock(List<UserInfo> users) {
+ // System user must be unlocked to unlock any other user
+ if (mCallbacks.isUserSecure(USER_SYSTEM) && !mStorage.hasRebootEscrow(USER_SYSTEM)) {
+ Slog.i(TAG, "No reboot escrow data found for system user");
+ return Collections.emptyList();
+ }
+
+ Set<Integer> noEscrowDataUsers = new HashSet<>();
for (UserInfo user : users) {
- if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) {
- rebootEscrowUsers.add(user);
+ if (mCallbacks.isUserSecure(user.id)
+ && !mStorage.hasRebootEscrow(user.id)) {
+ Slog.d(TAG, "No reboot escrow data found for user " + user);
+ noEscrowDataUsers.add(user.id);
}
}
+ List<UserInfo> rebootEscrowUsers = new ArrayList<>();
+ for (UserInfo user : users) {
+ // No lskf, no need to unlock.
+ if (!mCallbacks.isUserSecure(user.id)) {
+ continue;
+ }
+ // Don't unlock if user or user's parent does not have reboot data
+ int userId = user.id;
+ if (noEscrowDataUsers.contains(userId)
+ || noEscrowDataUsers.contains(
+ mInjector.getUserManagerInternal().getProfileParentId(userId))) {
+ continue;
+ }
+ rebootEscrowUsers.add(user);
+ }
+ return rebootEscrowUsers;
+ }
+
+ void loadRebootEscrowDataIfAvailable(Handler retryHandler) {
+ List<UserInfo> users = mUserManager.getUsers();
+ List<UserInfo> rebootEscrowUsers = getUsersToUnlock(users);
+
if (rebootEscrowUsers.isEmpty()) {
Slog.i(TAG, "No reboot escrow data found for users,"
+ " skipping loading escrow data");
+ setLoadEscrowDataErrorCode(ERROR_NO_REBOOT_ESCROW_DATA, retryHandler);
+ reportMetricOnRestoreComplete(
+ /* success= */ false, /* attemptCount= */ 1, retryHandler);
clearMetricsStorage();
return;
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 363684f..09605fe 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -59,7 +59,7 @@
public abstract void requestCreateSession(
long requestId,
String packageName,
- String routeId,
+ String routeOriginalId,
@Nullable Bundle sessionHints,
@RoutingSessionInfo.TransferReason int transferReason,
@NonNull UserHandle transferInitiatorUserHandle,
@@ -77,13 +77,15 @@
long requestId,
@NonNull UserHandle transferInitiatorUserHandle,
@NonNull String transferInitiatorPackageName,
- String sessionId,
- String routeId,
+ String sessionOriginalId,
+ String routeOriginalId,
@RoutingSessionInfo.TransferReason int transferReason);
- public abstract void setRouteVolume(long requestId, String routeId, int volume);
- public abstract void setSessionVolume(long requestId, String sessionId, int volume);
- public abstract void prepareReleaseSession(@NonNull String sessionId);
+ public abstract void setRouteVolume(long requestId, String routeOriginalId, int volume);
+
+ public abstract void setSessionVolume(long requestId, String sessionOriginalId, int volume);
+
+ public abstract void prepareReleaseSession(@NonNull String sessionUniqueId);
@NonNull
public String getUniqueId() {
@@ -197,8 +199,8 @@
*/
public final long mRequestId;
- /** The {@link MediaRoute2Info#getId() id} of the target route. */
- @NonNull public final String mTargetRouteId;
+ /** The {@link MediaRoute2Info#getOriginalId()} original id} of the target route. */
+ @NonNull public final String mTargetOriginalRouteId;
@RoutingSessionInfo.TransferReason public final int mTransferReason;
@@ -209,23 +211,23 @@
SessionCreationOrTransferRequest(
long requestId,
- @NonNull String routeId,
+ @NonNull String targetOriginalRouteId,
@RoutingSessionInfo.TransferReason int transferReason,
@NonNull UserHandle transferInitiatorUserHandle,
@NonNull String transferInitiatorPackageName) {
mRequestId = requestId;
- mTargetRouteId = routeId;
+ mTargetOriginalRouteId = targetOriginalRouteId;
mTransferReason = transferReason;
mTransferInitiatorUserHandle = transferInitiatorUserHandle;
mTransferInitiatorPackageName = transferInitiatorPackageName;
}
public boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) {
- return route2Info != null && mTargetRouteId.equals(route2Info.getId());
+ return route2Info != null && mTargetOriginalRouteId.equals(route2Info.getOriginalId());
}
- public boolean isTargetRouteIdInList(@NonNull List<String> routesList) {
- return routesList.stream().anyMatch(mTargetRouteId::equals);
+ public boolean isTargetRouteIdInList(@NonNull List<String> routeOriginalIdList) {
+ return routeOriginalIdList.stream().anyMatch(mTargetOriginalRouteId::equals);
}
}
}
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 386657e..71cbcb9 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -103,13 +103,14 @@
public void requestCreateSession(
long requestId,
String packageName,
- String routeId,
+ String routeOriginalId,
Bundle sessionHints,
@RoutingSessionInfo.TransferReason int transferReason,
@NonNull UserHandle transferInitiatorUserHandle,
@NonNull String transferInitiatorPackageName) {
if (mConnectionReady) {
- mActiveConnection.requestCreateSession(requestId, packageName, routeId, sessionHints);
+ mActiveConnection.requestCreateSession(
+ requestId, packageName, routeOriginalId, sessionHints);
updateBinding();
}
}
@@ -153,35 +154,35 @@
long requestId,
@NonNull UserHandle transferInitiatorUserHandle,
@NonNull String transferInitiatorPackageName,
- String sessionId,
- String routeId,
+ String sessionOriginalId,
+ String routeOriginalId,
@RoutingSessionInfo.TransferReason int transferReason) {
if (mConnectionReady) {
- mActiveConnection.transferToRoute(requestId, sessionId, routeId);
+ mActiveConnection.transferToRoute(requestId, sessionOriginalId, routeOriginalId);
}
}
@Override
- public void setRouteVolume(long requestId, String routeId, int volume) {
+ public void setRouteVolume(long requestId, String routeOriginalId, int volume) {
if (mConnectionReady) {
- mActiveConnection.setRouteVolume(requestId, routeId, volume);
+ mActiveConnection.setRouteVolume(requestId, routeOriginalId, volume);
updateBinding();
}
}
@Override
- public void setSessionVolume(long requestId, String sessionId, int volume) {
+ public void setSessionVolume(long requestId, String sessionOriginalId, int volume) {
if (mConnectionReady) {
- mActiveConnection.setSessionVolume(requestId, sessionId, volume);
+ mActiveConnection.setSessionVolume(requestId, sessionOriginalId, volume);
updateBinding();
}
}
@Override
- public void prepareReleaseSession(@NonNull String sessionId) {
+ public void prepareReleaseSession(@NonNull String sessionUniqueId) {
synchronized (mLock) {
for (RoutingSessionInfo session : mSessionInfos) {
- if (TextUtils.equals(session.getId(), sessionId)) {
+ if (TextUtils.equals(session.getId(), sessionUniqueId)) {
mSessionInfos.remove(session);
mReleasingSessions.add(session);
break;
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 76930a0..6b409ee 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -158,20 +158,20 @@
public void requestCreateSession(
long requestId,
String packageName,
- String routeId,
+ String routeOriginalId,
Bundle sessionHints,
@RoutingSessionInfo.TransferReason int transferReason,
@NonNull UserHandle transferInitiatorUserHandle,
@NonNull String transferInitiatorPackageName) {
// Assume a router without MODIFY_AUDIO_ROUTING permission can't request with
// a route ID different from the default route ID. The service should've filtered.
- if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
+ if (TextUtils.equals(routeOriginalId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
mCallback.onSessionCreated(this, requestId, mDefaultSessionInfo);
return;
}
if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
- if (TextUtils.equals(routeId, mSelectedRouteId)) {
+ if (TextUtils.equals(routeOriginalId, mSelectedRouteId)) {
RoutingSessionInfo currentSessionInfo;
synchronized (mLock) {
currentSessionInfo = mSessionInfos.get(0);
@@ -192,7 +192,7 @@
mPendingSessionCreationOrTransferRequest =
new SessionCreationOrTransferRequest(
requestId,
- routeId,
+ routeOriginalId,
RoutingSessionInfo.TRANSFER_REASON_FALLBACK,
transferInitiatorUserHandle,
transferInitiatorPackageName);
@@ -204,7 +204,7 @@
transferInitiatorUserHandle,
transferInitiatorPackageName,
SYSTEM_SESSION_ID,
- routeId,
+ routeOriginalId,
transferReason);
}
@@ -234,15 +234,15 @@
long requestId,
@NonNull UserHandle transferInitiatorUserHandle,
@NonNull String transferInitiatorPackageName,
- String sessionId,
- String routeId,
+ String sessionOriginalId,
+ String routeOriginalId,
@RoutingSessionInfo.TransferReason int transferReason) {
String selectedDeviceRouteId = mDeviceRouteController.getSelectedRoute().getId();
- if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
+ if (TextUtils.equals(routeOriginalId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
// Transfer to the default route (which is the selected route). We replace the id to
// be the selected route id so that the transfer reason gets updated.
- routeId = selectedDeviceRouteId;
+ routeOriginalId = selectedDeviceRouteId;
} else {
Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT);
return;
@@ -254,18 +254,18 @@
mPendingTransferRequest =
new SessionCreationOrTransferRequest(
requestId,
- routeId,
+ routeOriginalId,
transferReason,
transferInitiatorUserHandle,
transferInitiatorPackageName);
}
}
- String finalRouteId = routeId; // Make a final copy to use it in the lambda.
+ String finalRouteId = routeOriginalId; // Make a final copy to use it in the lambda.
boolean isAvailableDeviceRoute =
mDeviceRouteController.getAvailableRoutes().stream()
.anyMatch(it -> it.getId().equals(finalRouteId));
- boolean isSelectedDeviceRoute = TextUtils.equals(routeId, selectedDeviceRouteId);
+ boolean isSelectedDeviceRoute = TextUtils.equals(routeOriginalId, selectedDeviceRouteId);
if (isSelectedDeviceRoute || isAvailableDeviceRoute) {
// The requested route is managed by the device route controller. Note that the selected
@@ -273,12 +273,12 @@
// of the routing session). If the selected device route is transferred to, we need to
// make the bluetooth routes inactive so that the device route becomes the selected
// route of the routing session.
- mDeviceRouteController.transferTo(routeId);
+ mDeviceRouteController.transferTo(routeOriginalId);
mBluetoothRouteController.transferTo(null);
} else {
// The requested route is managed by the bluetooth route controller.
mDeviceRouteController.transferTo(null);
- mBluetoothRouteController.transferTo(routeId);
+ mBluetoothRouteController.transferTo(routeOriginalId);
}
if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()
@@ -288,20 +288,20 @@
}
@Override
- public void setRouteVolume(long requestId, String routeId, int volume) {
- if (!TextUtils.equals(routeId, mSelectedRouteId)) {
+ public void setRouteVolume(long requestId, String routeOriginalId, int volume) {
+ if (!TextUtils.equals(routeOriginalId, mSelectedRouteId)) {
return;
}
mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0);
}
@Override
- public void setSessionVolume(long requestId, String sessionId, int volume) {
+ public void setSessionVolume(long requestId, String sessionOriginalId, int volume) {
// Do nothing since we don't support grouping volume yet.
}
@Override
- public void prepareReleaseSession(String sessionId) {
+ public void prepareReleaseSession(String sessionUniqueId) {
// Do nothing since the system session persists.
}
@@ -503,12 +503,13 @@
}
long pendingRequestId = mPendingSessionCreationOrTransferRequest.mRequestId;
- if (mPendingSessionCreationOrTransferRequest.mTargetRouteId.equals(mSelectedRouteId)) {
+ if (mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId.equals(
+ mSelectedRouteId)) {
if (DEBUG) {
Slog.w(
TAG,
"Session creation success to route "
- + mPendingSessionCreationOrTransferRequest.mTargetRouteId);
+ + mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId);
}
mPendingSessionCreationOrTransferRequest = null;
mCallback.onSessionCreated(this, pendingRequestId, newSessionInfo);
@@ -520,7 +521,8 @@
Slog.w(
TAG,
"Session creation failed to route "
- + mPendingSessionCreationOrTransferRequest.mTargetRouteId);
+ + mPendingSessionCreationOrTransferRequest
+ .mTargetOriginalRouteId);
}
mPendingSessionCreationOrTransferRequest = null;
mCallback.onRequestFailed(
@@ -529,7 +531,7 @@
Slog.w(
TAG,
"Session creation waiting state to route "
- + mPendingSessionCreationOrTransferRequest.mTargetRouteId);
+ + mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId);
}
}
}
@@ -541,7 +543,8 @@
// See b/307723189 for context
for (MediaRoute2Info btRoute : mBluetoothRouteController.getAllBluetoothRoutes()) {
if (TextUtils.equals(
- btRoute.getId(), mPendingSessionCreationOrTransferRequest.mTargetRouteId)) {
+ btRoute.getId(),
+ mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId)) {
return true;
}
}
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index 3949dfe..1a8e44b 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1626,8 +1626,8 @@
ApplicationInfo appInfo = null;
try {
- appInfo = mContext.getPackageManager().getApplicationInfo(
- name.getPackageName(), 0);
+ appInfo = mContext.getPackageManager().getApplicationInfoAsUser(
+ name.getPackageName(), 0, userid);
} catch (NameNotFoundException e) {
// Ignore if the package doesn't exist we won't be able to bind to the service.
}
diff --git a/services/core/java/com/android/server/pm/KillAppBlocker.java b/services/core/java/com/android/server/pm/KillAppBlocker.java
new file mode 100644
index 0000000..e2901c3
--- /dev/null
+++ b/services/core/java/com/android/server/pm/KillAppBlocker.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm;
+
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.content.pm.PackageManager.MATCH_ALL;
+import static android.os.Process.INVALID_UID;
+
+import android.app.ActivityManager;
+import android.app.ActivityManagerInternal;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
+import android.app.UidObserver;
+import android.os.Process;
+import android.os.RemoteException;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Use to monitor UIDs are really killed by the {@link IUidObserver}
+ */
+final class KillAppBlocker {
+ private static final int MAX_WAIT_TIMEOUT_MS = 1000;
+ private CountDownLatch mUidsGoneCountDownLatch = new CountDownLatch(1);
+ private List mActiveUids = new ArrayList();
+ private boolean mRegistered = false;
+
+ private final IUidObserver mUidObserver = new UidObserver() {
+ @Override
+ public void onUidGone(int uid, boolean disabled) {
+ synchronized (this) {
+ mActiveUids.remove((Integer) uid);
+
+ if (mActiveUids.size() == 0) {
+ mUidsGoneCountDownLatch.countDown();
+ }
+ }
+ }
+ };
+
+ void register() {
+ if (!mRegistered) {
+ IActivityManager am = ActivityManager.getService();
+ if (am != null) {
+ try {
+ am.registerUidObserver(mUidObserver, ActivityManager.UID_OBSERVER_GONE,
+ ActivityManager.PROCESS_STATE_UNKNOWN, "pm");
+ mRegistered = true;
+ } catch (RemoteException e) {
+ // no-op
+ }
+ }
+ }
+ }
+
+ void unregister() {
+ if (mRegistered) {
+ IActivityManager am = ActivityManager.getService();
+ if (am != null) {
+ try {
+ mRegistered = false;
+ am.unregisterUidObserver(mUidObserver);
+ } catch (RemoteException e) {
+ // no-op
+ }
+ }
+ }
+ }
+
+ void waitAppProcessGone(ActivityManagerInternal mAmi, Computer snapshot,
+ UserManagerService userManager, String packageName) {
+ if (!mRegistered) {
+ return;
+ }
+ synchronized (this) {
+ if (mAmi != null) {
+ int[] users = userManager.getUserIds();
+
+ for (int i = 0; i < users.length; i++) {
+ final int userId = users[i];
+ final int uid = snapshot.getPackageUidInternal(
+ packageName, MATCH_ALL, userId, Process.SYSTEM_UID);
+ if (uid != INVALID_UID) {
+ if (mAmi.getUidProcessState(uid) != PROCESS_STATE_NONEXISTENT) {
+ mActiveUids.add(uid);
+ }
+ }
+ }
+ }
+ }
+
+ try {
+ mUidsGoneCountDownLatch.await(MAX_WAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ // no-op
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index dec97fb..0d1095f 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -704,7 +704,8 @@
return false;
}
- if (isAppOptedOutOfArchiving(packageName, ps.getAppId())) {
+ if (isAppOptedOutOfArchiving(packageName,
+ UserHandle.getUid(userId, ps.getAppId()))) {
return false;
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index f8fceda..679ab65 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3136,13 +3136,18 @@
void killApplicationSync(String pkgName, @AppIdInt int appId,
@UserIdInt int userId, String reason, int exitInfoReason) {
ActivityManagerInternal mAmi = LocalServices.getService(ActivityManagerInternal.class);
- if (mAmi != null) {
- if (Thread.holdsLock(mLock)) {
- // holds PM's lock, go back killApplication to avoid it run into watchdog reset.
- Slog.e(TAG, "Holds PM's locker, unable kill application synchronized");
- killApplication(pkgName, appId, userId, reason, exitInfoReason);
- } else {
+ if (Thread.holdsLock(mLock) || mAmi == null) {
+ // holds PM's lock, go back killApplication to avoid it run into watchdog reset.
+ Slog.e(TAG, "Holds PM's lock, unable kill application synchronized");
+ killApplication(pkgName, appId, userId, reason, exitInfoReason);
+ } else {
+ KillAppBlocker blocker = new KillAppBlocker();
+ try {
+ blocker.register();
mAmi.killApplicationSync(pkgName, appId, userId, reason, exitInfoReason);
+ blocker.waitAppProcessGone(mAmi, snapshotComputer(), mUserManager, pkgName);
+ } finally {
+ blocker.unregister();
}
}
}
@@ -4052,9 +4057,6 @@
return;
}
- // Log the metrics when the component state is changed.
- PackageMetrics.reportComponentStateChanged(computer, componentStateMetricsList, userId);
-
if (isSynchronous) {
flushPackageRestrictionsAsUserInternalLocked(userId);
} else {
@@ -4074,6 +4076,10 @@
}
}
+ // Log the metrics when the component state is changed.
+ PackageMetrics.reportComponentStateChanged(snapshotComputer(), componentStateMetricsList,
+ userId);
+
final long callingId = Binder.clearCallingIdentity();
try {
final Computer newSnapshot = snapshotComputer();
diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
index c8bcc51..e753ce8 100644
--- a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
+++ b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java
@@ -926,7 +926,9 @@
}
Slog.i(TAG, "Enabling rollback for install of " + packageName
+ ", session:" + session.sessionId
- + ", rollbackDataPolicy=" + rollbackDataPolicy);
+ + ", rollbackDataPolicy=" + rollbackDataPolicy
+ + ", rollbackId:" + rollback.info.getRollbackId()
+ + ", originalSessionId:" + rollback.getOriginalSessionId());
final String installerPackageName = session.getInstallerPackageName();
if (!enableRollbackAllowed(installerPackageName, packageName)) {
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index e00e813..04db3e8 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -72,7 +72,6 @@
import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.Xml;
@@ -88,6 +87,7 @@
import com.android.server.SystemService;
import com.android.server.servicewatcher.CurrentUserServiceSupplier;
import com.android.server.servicewatcher.ServiceWatcher;
+import com.android.server.utils.Slogf;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -98,7 +98,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
-
+import java.util.Objects;
/**
* Manages trust agents and trust listeners.
@@ -275,6 +275,10 @@
return KeyStoreAuthorization.getInstance();
}
+ AlarmManager getAlarmManager() {
+ return mContext.getSystemService(AlarmManager.class);
+ }
+
Looper getLooper() {
return Looper.myLooper();
}
@@ -293,7 +297,7 @@
mLockPatternUtils = injector.getLockPatternUtils();
mKeyStoreAuthorization = injector.getKeyStoreAuthorization();
mStrongAuthTracker = new StrongAuthTracker(context, injector.getLooper());
- mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+ mAlarmManager = injector.getAlarmManager();
}
@Override
@@ -362,6 +366,13 @@
}
private void scheduleTrustTimeout(boolean override, boolean isTrustableTimeout) {
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "scheduleTrustTimeout(override=%s, isTrustable=%s)",
+ override,
+ isTrustableTimeout);
+ }
int shouldOverride = override ? 1 : 0;
int trustableTimeout = isTrustableTimeout ? 1 : 0;
mHandler.obtainMessage(MSG_SCHEDULE_TRUST_TIMEOUT, shouldOverride,
@@ -370,6 +381,13 @@
private void handleScheduleTrustTimeout(boolean shouldOverride, TimeoutType timeoutType) {
int userId = mCurrentUser;
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "handleScheduleTrustTimeout(shouldOverride=%s, timeoutType=%s)",
+ shouldOverride,
+ timeoutType);
+ }
if (timeoutType == TimeoutType.TRUSTABLE) {
// don't override the hard timeout unless biometric or knowledge factor authentication
// occurs which isn't where this is called from. Override the idle timeout what the
@@ -383,6 +401,7 @@
/* Override both the idle and hard trustable timeouts */
private void refreshTrustableTimers(int userId) {
+ if (DEBUG) Slogf.d(TAG, "refreshTrustableTimers(userId=%s)", userId);
handleScheduleTrustableTimeouts(userId, true /* overrideIdleTimeout */,
true /* overrideHardTimeout */);
}
@@ -405,13 +424,20 @@
}
private void handleScheduleTrustedTimeout(int userId, boolean shouldOverride) {
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "handleScheduleTrustedTimeout(userId=%s, shouldOverride=%s)",
+ userId,
+ shouldOverride);
+ }
long when = SystemClock.elapsedRealtime() + TRUST_TIMEOUT_IN_MILLIS;
TrustedTimeoutAlarmListener alarm = mTrustTimeoutAlarmListenerForUser.get(userId);
// Cancel existing trust timeouts for this user if needed.
if (alarm != null) {
if (!shouldOverride && alarm.isQueued()) {
- if (DEBUG) Slog.d(TAG, "Found existing trust timeout alarm. Skipping.");
+ if (DEBUG) Slogf.d(TAG, "Found existing trust timeout alarm. Skipping.");
return;
}
mAlarmManager.cancel(alarm);
@@ -420,7 +446,9 @@
mTrustTimeoutAlarmListenerForUser.put(userId, alarm);
}
- if (DEBUG) Slog.d(TAG, "\tSetting up trust timeout alarm");
+ if (DEBUG) {
+ Slogf.d(TAG, "\tSetting up trust timeout alarm triggering at elapsedRealTime=%s", when);
+ }
alarm.setQueued(true /* isQueued */);
mAlarmManager.setExact(
AlarmManager.ELAPSED_REALTIME_WAKEUP, when, TRUST_TIMEOUT_ALARM_TAG, alarm,
@@ -434,6 +462,13 @@
}
private void setUpIdleTimeout(int userId, boolean overrideIdleTimeout) {
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "setUpIdleTimeout(userId=%s, overrideIdleTimeout=%s)",
+ userId,
+ overrideIdleTimeout);
+ }
long when = SystemClock.elapsedRealtime() + TRUSTABLE_IDLE_TIMEOUT_IN_MILLIS;
TrustableTimeoutAlarmListener alarm = mIdleTrustableTimeoutAlarmListenerForUser.get(userId);
mContext.enforceCallingOrSelfPermission(Manifest.permission.SCHEDULE_EXACT_ALARM, null);
@@ -441,7 +476,7 @@
// Cancel existing trustable timeouts for this user if needed.
if (alarm != null) {
if (!overrideIdleTimeout && alarm.isQueued()) {
- if (DEBUG) Slog.d(TAG, "Found existing trustable timeout alarm. Skipping.");
+ if (DEBUG) Slogf.d(TAG, "Found existing trustable timeout alarm. Skipping.");
return;
}
mAlarmManager.cancel(alarm);
@@ -450,7 +485,12 @@
mIdleTrustableTimeoutAlarmListenerForUser.put(userId, alarm);
}
- if (DEBUG) Slog.d(TAG, "\tSetting up trustable idle timeout alarm");
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "\tSetting up trustable idle timeout alarm triggering at elapsedRealTime=%s",
+ when);
+ }
alarm.setQueued(true /* isQueued */);
mAlarmManager.setExact(
AlarmManager.ELAPSED_REALTIME_WAKEUP, when, TRUST_TIMEOUT_ALARM_TAG, alarm,
@@ -458,6 +498,13 @@
}
private void setUpHardTimeout(int userId, boolean overrideHardTimeout) {
+ if (DEBUG) {
+ Slogf.i(
+ TAG,
+ "setUpHardTimeout(userId=%s, overrideHardTimeout=%s)",
+ userId,
+ overrideHardTimeout);
+ }
mContext.enforceCallingOrSelfPermission(Manifest.permission.SCHEDULE_EXACT_ALARM, null);
TrustableTimeoutAlarmListener alarm = mTrustableTimeoutAlarmListenerForUser.get(userId);
@@ -472,7 +519,13 @@
} else if (overrideHardTimeout) {
mAlarmManager.cancel(alarm);
}
- if (DEBUG) Slog.d(TAG, "\tSetting up trustable hard timeout alarm");
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "\tSetting up trustable hard timeout alarm triggering at "
+ + "elapsedRealTime=%s",
+ when);
+ }
alarm.setQueued(true /* isQueued */);
mAlarmManager.setExact(
AlarmManager.ELAPSED_REALTIME_WAKEUP, when, TRUST_TIMEOUT_ALARM_TAG, alarm,
@@ -503,6 +556,12 @@
public int hashCode() {
return component.hashCode() * 31 + userId;
}
+
+ @Override
+ public String toString() {
+ return String.format(
+ "AgentInfo{label=%s, component=%s, userId=%s}", label, component, userId);
+ }
}
private void updateTrustAll() {
@@ -532,6 +591,15 @@
int flags,
boolean isFromUnlock,
@Nullable AndroidFuture<GrantTrustResult> resultCallback) {
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "updateTrust(userId=%s, flags=%s, isFromUnlock=%s, resultCallbackPresent=%s)",
+ userId,
+ flags,
+ isFromUnlock,
+ Objects.isNull(resultCallback));
+ }
boolean managed = aggregateIsTrustManaged(userId);
dispatchOnTrustManagedChanged(managed, userId);
if (mStrongAuthTracker.isTrustAllowedForUser(userId)
@@ -559,27 +627,50 @@
(flags & TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) != 0);
boolean canMoveToTrusted =
alreadyUnlocked || isFromUnlock || renewingTrust || isAutomotive();
- boolean upgradingTrustForCurrentUser = (userId == mCurrentUser);
+ boolean updatingTrustForCurrentUser = (userId == mCurrentUser);
+
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "updateTrust: alreadyUnlocked=%s, wasTrusted=%s, wasTrustable=%s, "
+ + "renewingTrust=%s, canMoveToTrusted=%s, "
+ + "updatingTrustForCurrentUser=%s",
+ alreadyUnlocked,
+ wasTrusted,
+ wasTrustable,
+ renewingTrust,
+ canMoveToTrusted,
+ updatingTrustForCurrentUser);
+ }
if (trustedByAtLeastOneAgent && wasTrusted) {
// no change
return;
- } else if (trustedByAtLeastOneAgent && canMoveToTrusted
- && upgradingTrustForCurrentUser) {
+ } else if (trustedByAtLeastOneAgent
+ && canMoveToTrusted
+ && updatingTrustForCurrentUser) {
pendingTrustState = TrustState.TRUSTED;
- } else if (trustableByAtLeastOneAgent && (wasTrusted || wasTrustable)
- && upgradingTrustForCurrentUser) {
+ } else if (trustableByAtLeastOneAgent
+ && (wasTrusted || wasTrustable)
+ && updatingTrustForCurrentUser) {
pendingTrustState = TrustState.TRUSTABLE;
} else {
pendingTrustState = TrustState.UNTRUSTED;
}
+ if (DEBUG) Slogf.d(TAG, "updateTrust: pendingTrustState=%s", pendingTrustState);
mUserTrustState.put(userId, pendingTrustState);
}
- if (DEBUG) Slog.d(TAG, "pendingTrustState: " + pendingTrustState);
boolean isNowTrusted = pendingTrustState == TrustState.TRUSTED;
boolean newlyUnlocked = !alreadyUnlocked && isNowTrusted;
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "updateTrust: isNowTrusted=%s, newlyUnlocked=%s",
+ isNowTrusted,
+ newlyUnlocked);
+ }
maybeActiveUnlockRunningChanged(userId);
dispatchOnTrustChanged(
isNowTrusted, newlyUnlocked, userId, flags, getTrustGrantedMessages(userId));
@@ -598,13 +689,13 @@
boolean shouldSendCallback = newlyUnlocked;
if (shouldSendCallback) {
if (resultCallback != null) {
- if (DEBUG) Slog.d(TAG, "calling back with UNLOCKED_BY_GRANT");
+ if (DEBUG) Slogf.d(TAG, "calling back with UNLOCKED_BY_GRANT");
resultCallback.complete(new GrantTrustResult(STATUS_UNLOCKED_BY_GRANT));
}
}
if ((wasTrusted || wasTrustable) && pendingTrustState == TrustState.UNTRUSTED) {
- if (DEBUG) Slog.d(TAG, "Trust was revoked, destroy trustable alarms");
+ if (DEBUG) Slogf.d(TAG, "Trust was revoked, destroy trustable alarms");
cancelBothTrustableAlarms(userId);
}
}
@@ -650,7 +741,7 @@
try {
WindowManagerGlobal.getWindowManagerService().lockNow(null);
} catch (RemoteException e) {
- Slog.e(TAG, "Error locking screen when called from trust agent");
+ Slogf.e(TAG, "Error locking screen when called from trust agent");
}
}
@@ -659,8 +750,9 @@
}
void refreshAgentList(int userIdOrAll) {
- if (DEBUG) Slog.d(TAG, "refreshAgentList(" + userIdOrAll + ")");
+ if (DEBUG) Slogf.d(TAG, "refreshAgentList(userIdOrAll=%s)", userIdOrAll);
if (!mTrustAgentsCanRun) {
+ if (DEBUG) Slogf.d(TAG, "Did not refresh agent list because agents cannot run.");
return;
}
if (userIdOrAll != UserHandle.USER_ALL && userIdOrAll < UserHandle.USER_SYSTEM) {
@@ -686,18 +778,30 @@
if (userInfo == null || userInfo.partial || !userInfo.isEnabled()
|| userInfo.guestToRemove) continue;
if (!userInfo.supportsSwitchToByUser()) {
- if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
- + ": switchToByUser=false");
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "refreshAgentList: skipping user %s: switchToByUser=false",
+ userInfo.id);
+ }
continue;
}
if (!mActivityManager.isUserRunning(userInfo.id)) {
- if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
- + ": user not started");
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "refreshAgentList: skipping user %s: user not started",
+ userInfo.id);
+ }
continue;
}
if (!lockPatternUtils.isSecure(userInfo.id)) {
- if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
- + ": no secure credential");
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "refreshAgentList: skipping user %s: no secure credential",
+ userInfo.id);
+ }
continue;
}
@@ -708,8 +812,12 @@
List<ComponentName> enabledAgents = lockPatternUtils.getEnabledTrustAgents(userInfo.id);
if (enabledAgents.isEmpty()) {
- if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
- + ": no agents enabled by user");
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "refreshAgentList: skipping user %s: no agents enabled by user",
+ userInfo.id);
+ }
continue;
}
List<ResolveInfo> resolveInfos = resolveAllowedTrustAgents(pm, userInfo.id);
@@ -717,9 +825,13 @@
ComponentName name = getComponentName(resolveInfo);
if (!enabledAgents.contains(name)) {
- if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping "
- + name.flattenToShortString() + " u"+ userInfo.id
- + ": not enabled by user");
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "refreshAgentList: skipping %s u%s: not enabled by user",
+ name.flattenToShortString(),
+ userInfo.id);
+ }
continue;
}
if (disableTrustAgents) {
@@ -727,9 +839,13 @@
dpm.getTrustAgentConfiguration(null /* admin */, name, userInfo.id);
// Disable agent if no features are enabled.
if (config == null || config.isEmpty()) {
- if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping "
- + name.flattenToShortString() + " u"+ userInfo.id
- + ": not allowed by DPM");
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "refreshAgentList: skipping %s u%s: not allowed by DPM",
+ name.flattenToShortString(),
+ userInfo.id);
+ }
continue;
}
}
@@ -752,15 +868,26 @@
}
if (directUnlock) {
- if (DEBUG) Slog.d(TAG, "refreshAgentList: trustagent " + name
- + "of user " + userInfo.id + "can unlock user profile.");
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "refreshAgentList: trustagent %s of user %s can unlock user "
+ + "profile.",
+ name,
+ userInfo.id);
+ }
}
if (!mUserManager.isUserUnlockingOrUnlocked(userInfo.id)
&& !directUnlock) {
- if (DEBUG) Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
- + "'s trust agent " + name + ": FBE still locked and "
- + " the agent cannot unlock user profile.");
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "refreshAgentList: skipping user %s's trust agent %s: FBE still "
+ + "locked and the agent cannot unlock user profile.",
+ userInfo.id,
+ name);
+ }
continue;
}
@@ -769,11 +896,16 @@
if (flag != StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT) {
if (flag != StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
|| !directUnlock) {
- if (DEBUG)
- Slog.d(TAG, "refreshAgentList: skipping user " + userInfo.id
- + ": prevented by StrongAuthTracker = 0x"
- + Integer.toHexString(mStrongAuthTracker.getStrongAuthForUser(
- userInfo.id)));
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "refreshAgentList: skipping user %s: prevented by "
+ + "StrongAuthTracker = 0x%s",
+ userInfo.id,
+ Integer.toHexString(
+ mStrongAuthTracker.getStrongAuthForUser(
+ userInfo.id)));
+ }
continue;
}
}
@@ -804,6 +936,15 @@
}
}
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "refreshAgentList: userInfos=%s, obsoleteAgents=%s, trustMayHaveChanged=%s",
+ userInfos,
+ obsoleteAgents,
+ trustMayHaveChanged);
+ }
+
if (trustMayHaveChanged) {
if (userIdOrAll == UserHandle.USER_ALL) {
updateTrustAll();
@@ -1044,7 +1185,7 @@
parser = resolveInfo.serviceInfo.loadXmlMetaData(pm,
TrustAgentService.TRUST_AGENT_META_DATA);
if (parser == null) {
- Slog.w(TAG, "Can't find " + TrustAgentService.TRUST_AGENT_META_DATA + " meta-data");
+ Slogf.w(TAG, "Can't find %s meta-data", TrustAgentService.TRUST_AGENT_META_DATA);
return null;
}
Resources res = pm.getResourcesForApplication(resolveInfo.serviceInfo.applicationInfo);
@@ -1056,7 +1197,7 @@
}
String nodeName = parser.getName();
if (!"trust-agent".equals(nodeName)) {
- Slog.w(TAG, "Meta-data does not start with trust-agent tag");
+ Slogf.w(TAG, "Meta-data does not start with trust-agent tag");
return null;
}
TypedArray sa = res
@@ -1075,7 +1216,11 @@
if (parser != null) parser.close();
}
if (caughtException != null) {
- Slog.w(TAG, "Error parsing : " + resolveInfo.serviceInfo.packageName, caughtException);
+ Slogf.w(
+ TAG,
+ caughtException,
+ "Error parsing : %s",
+ resolveInfo.serviceInfo.packageName);
return null;
}
if (cn == null) {
@@ -1242,13 +1387,18 @@
// Agent dispatch and aggregation
private boolean aggregateIsTrusted(int userId) {
+ if (DEBUG) Slogf.d(TAG, "aggregateIsTrusted(userId=%s)", userId);
if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
+ if (DEBUG) {
+ Slogf.d(TAG, "not trusted because trust not allowed for userId=%s", userId);
+ }
return false;
}
for (int i = 0; i < mActiveAgents.size(); i++) {
AgentInfo info = mActiveAgents.valueAt(i);
if (info.userId == userId) {
if (info.agent.isTrusted()) {
+ if (DEBUG) Slogf.d(TAG, "trusted by %s", info);
return true;
}
}
@@ -1257,13 +1407,18 @@
}
private boolean aggregateIsTrustable(int userId) {
+ if (DEBUG) Slogf.d(TAG, "aggregateIsTrustable(userId=%s)", userId);
if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
+ if (DEBUG) {
+ Slogf.d(TAG, "not trustable because trust not allowed for userId=%s", userId);
+ }
return false;
}
for (int i = 0; i < mActiveAgents.size(); i++) {
AgentInfo info = mActiveAgents.valueAt(i);
if (info.userId == userId) {
if (info.agent.isTrustable()) {
+ if (DEBUG) Slogf.d(TAG, "trustable by %s", info);
return true;
}
}
@@ -1328,20 +1483,31 @@
private boolean aggregateIsTrustManaged(int userId) {
if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) {
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "trust not managed due to trust not being allowed for userId=%s",
+ userId);
+ }
return false;
}
for (int i = 0; i < mActiveAgents.size(); i++) {
AgentInfo info = mActiveAgents.valueAt(i);
if (info.userId == userId) {
if (info.agent.isManagingTrust()) {
+ if (DEBUG) Slogf.d(TAG, "trust managed for userId=%s", userId);
return true;
}
}
}
+ if (DEBUG) Slogf.d(TAG, "trust not managed for userId=%s", userId);
return false;
}
private void dispatchUnlockAttempt(boolean successful, int userId) {
+ if (DEBUG) {
+ Slogf.d(TAG, "dispatchUnlockAttempt(successful=%s, userId=%s)", successful, userId);
+ }
if (successful) {
mStrongAuthTracker.allowTrustFromUnlock(userId);
// Allow the presence of trust on a successful unlock attempt to extend unlock
@@ -1359,8 +1525,11 @@
private void dispatchUserRequestedUnlock(int userId, boolean dismissKeyguard) {
if (DEBUG) {
- Slog.d(TAG, "dispatchUserRequestedUnlock(user=" + userId + ", dismissKeyguard="
- + dismissKeyguard + ")");
+ Slogf.d(
+ TAG,
+ "dispatchUserRequestedUnlock(user=%s, dismissKeyguard=%s)",
+ userId,
+ dismissKeyguard);
}
for (int i = 0; i < mActiveAgents.size(); i++) {
AgentInfo info = mActiveAgents.valueAt(i);
@@ -1372,7 +1541,7 @@
private void dispatchUserMayRequestUnlock(int userId) {
if (DEBUG) {
- Slog.d(TAG, "dispatchUserMayRequestUnlock(user=" + userId + ")");
+ Slogf.d(TAG, "dispatchUserMayRequestUnlock(user=%s)", userId);
}
for (int i = 0; i < mActiveAgents.size(); i++) {
AgentInfo info = mActiveAgents.valueAt(i);
@@ -1405,9 +1574,9 @@
try {
listener.onIsActiveUnlockRunningChanged(isRunning, userId);
} catch (DeadObjectException e) {
- Slog.d(TAG, "TrustListener dead while trying to notify Active Unlock running state");
+ Slogf.d(TAG, "TrustListener dead while trying to notify Active Unlock running state");
} catch (RemoteException e) {
- Slog.e(TAG, "Exception while notifying TrustListener.", e);
+ Slogf.e(TAG, "Exception while notifying TrustListener.", e);
}
}
@@ -1445,11 +1614,11 @@
mTrustListeners.get(i).onTrustChanged(
enabled, newlyUnlocked, userId, flags, trustGrantedMessages);
} catch (DeadObjectException e) {
- Slog.d(TAG, "Removing dead TrustListener.");
+ Slogf.d(TAG, "Removing dead TrustListener.");
mTrustListeners.remove(i);
i--;
} catch (RemoteException e) {
- Slog.e(TAG, "Exception while notifying TrustListener.", e);
+ Slogf.e(TAG, "Exception while notifying TrustListener.", e);
}
}
}
@@ -1462,11 +1631,11 @@
try {
mTrustListeners.get(i).onEnabledTrustAgentsChanged(userId);
} catch (DeadObjectException e) {
- Slog.d(TAG, "Removing dead TrustListener.");
+ Slogf.d(TAG, "Removing dead TrustListener.");
mTrustListeners.remove(i);
i--;
} catch (RemoteException e) {
- Slog.e(TAG, "Exception while notifying TrustListener.", e);
+ Slogf.e(TAG, "Exception while notifying TrustListener.", e);
}
}
}
@@ -1479,11 +1648,11 @@
try {
mTrustListeners.get(i).onTrustManagedChanged(managed, userId);
} catch (DeadObjectException e) {
- Slog.d(TAG, "Removing dead TrustListener.");
+ Slogf.d(TAG, "Removing dead TrustListener.");
mTrustListeners.remove(i);
i--;
} catch (RemoteException e) {
- Slog.e(TAG, "Exception while notifying TrustListener.", e);
+ Slogf.e(TAG, "Exception while notifying TrustListener.", e);
}
}
}
@@ -1496,11 +1665,11 @@
try {
mTrustListeners.get(i).onTrustError(message);
} catch (DeadObjectException e) {
- Slog.d(TAG, "Removing dead TrustListener.");
+ Slogf.d(TAG, "Removing dead TrustListener.");
mTrustListeners.remove(i);
i--;
} catch (RemoteException e) {
- Slog.e(TAG, "Exception while notifying TrustListener.", e);
+ Slogf.e(TAG, "Exception while notifying TrustListener.", e);
}
}
}
@@ -1535,7 +1704,7 @@
&& mFingerprintManager.hasEnrolledTemplates(userId)
&& isWeakOrConvenienceSensor(
mFingerprintManager.getSensorProperties().get(0))) {
- Slog.i(TAG, "User is unlockable by non-strong fingerprint auth");
+ Slogf.i(TAG, "User is unlockable by non-strong fingerprint auth");
return true;
}
@@ -1543,7 +1712,7 @@
&& (disabledFeatures & DevicePolicyManager.KEYGUARD_DISABLE_FACE) == 0
&& mFaceManager.hasEnrolledTemplates(userId)
&& isWeakOrConvenienceSensor(mFaceManager.getSensorProperties().get(0))) {
- Slog.i(TAG, "User is unlockable by non-strong face auth");
+ Slogf.i(TAG, "User is unlockable by non-strong face auth");
return true;
}
}
@@ -1551,7 +1720,7 @@
// Check whether it's possible for the device to be actively unlocked by a trust agent.
if (getUserTrustStateInner(userId) == TrustState.TRUSTABLE
|| (isAutomotive() && isTrustUsuallyManagedInternal(userId))) {
- Slog.i(TAG, "User is unlockable by trust agent");
+ Slogf.i(TAG, "User is unlockable by trust agent");
return true;
}
@@ -1595,6 +1764,13 @@
private final IBinder mService = new ITrustManager.Stub() {
@Override
public void reportUnlockAttempt(boolean authenticated, int userId) throws RemoteException {
+ if (DEBUG) {
+ Slogf.d(
+ TAG,
+ "reportUnlockAttempt(authenticated=%s, userId=%s)",
+ authenticated,
+ userId);
+ }
enforceReportPermission();
mHandler.obtainMessage(MSG_DISPATCH_UNLOCK_ATTEMPT, authenticated ? 1 : 0, userId)
.sendToTarget();
@@ -1611,7 +1787,8 @@
@Override
public void reportUserMayRequestUnlock(int userId) throws RemoteException {
enforceReportPermission();
- mHandler.obtainMessage(MSG_USER_MAY_REQUEST_UNLOCK, userId, /*arg2=*/ 0).sendToTarget();
+ mHandler.obtainMessage(MSG_USER_MAY_REQUEST_UNLOCK, userId, /* arg2= */ 0)
+ .sendToTarget();
}
@Override
@@ -1932,6 +2109,7 @@
return new Handler(looper) {
@Override
public void handleMessage(Message msg) {
+ if (DEBUG) Slogf.d(TAG, "handler: %s", msg.what);
switch (msg.what) {
case MSG_REGISTER_LISTENER:
addListener((ITrustListener) msg.obj);
@@ -2002,8 +2180,24 @@
handleScheduleTrustTimeout(shouldOverride, timeoutType);
break;
case MSG_REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH:
+ if (DEBUG) {
+ Slogf.d(TAG, "REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH userId=%s", msg.arg1);
+ }
TrustableTimeoutAlarmListener trustableAlarm =
mTrustableTimeoutAlarmListenerForUser.get(msg.arg1);
+ if (DEBUG) {
+ if (trustableAlarm != null) {
+ Slogf.d(
+ TAG,
+ "REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH trustable alarm "
+ + "isQueued=%s",
+ trustableAlarm.mIsQueued);
+ } else {
+ Slogf.d(
+ TAG,
+ "REFRESH_TRUSTABLE_TIMERS_AFTER_AUTH no trustable alarm");
+ }
+ }
if (trustableAlarm != null && trustableAlarm.isQueued()) {
refreshTrustableTimers(msg.arg1);
}
@@ -2194,7 +2388,7 @@
handleAlarm();
// Only fire if trust can unlock.
if (mStrongAuthTracker.isTrustAllowedForUser(mUserId)) {
- if (DEBUG) Slog.d(TAG, "Revoking all trust because of trust timeout");
+ if (DEBUG) Slogf.d(TAG, "Revoking all trust because of trust timeout");
mLockPatternUtils.requireStrongAuth(
mStrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_TRUSTAGENT_EXPIRED, mUserId);
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 0048849..2d2a88a86 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3964,7 +3964,7 @@
}
if (isCurrentVisible) {
- if (isNextNotYetVisible || delayRemoval) {
+ if (isNextNotYetVisible || delayRemoval || (next != null && isInTransition())) {
// Add this activity to the list of stopping activities. It will be processed and
// destroyed when the next activity reports idle.
addToStopping(false /* scheduleIdle */, false /* idleDelayed */,
@@ -7644,6 +7644,8 @@
// This could only happen when the window is removed from hierarchy. So do not keep its
// reference anymore.
mStartingWindow = null;
+ mStartingData = null;
+ mStartingSurface = null;
}
if (mChildren.size() == 0 && mVisibleSetFromTransferredStartingWindow) {
// We set the visible state to true for the token from a transferred starting
@@ -9259,6 +9261,19 @@
@NonNull CompatDisplayInsets compatDisplayInsets) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
+ final Insets insets;
+ if (mResolveConfigHint.mUseOverrideInsetsForConfig) {
+ // TODO(b/343197837): Add test to verify SCM behaviour with new bound configuration
+ // Insets are decoupled from configuration by default from V+, use legacy
+ // compatibility behaviour for apps targeting SDK earlier than 35
+ // (see applySizeOverrideIfNeeded).
+ insets = Insets.of(mDisplayContent.getDisplayPolicy()
+ .getDecorInsetsInfo(mDisplayContent.mDisplayFrames.mRotation,
+ mDisplayContent.mDisplayFrames.mWidth,
+ mDisplayContent.mDisplayFrames.mHeight).mOverrideNonDecorInsets);
+ } else {
+ insets = Insets.NONE;
+ }
// When an activity needs to be letterboxed because of fixed orientation, use fixed
// orientation bounds (stored in resolved bounds) instead of parent bounds since the
@@ -9269,9 +9284,12 @@
final Rect containerBounds = useResolvedBounds
? new Rect(resolvedBounds)
: newParentConfiguration.windowConfiguration.getBounds();
+ final Rect parentAppBounds =
+ newParentConfiguration.windowConfiguration.getAppBounds();
+ parentAppBounds.inset(insets);
final Rect containerAppBounds = useResolvedBounds
? new Rect(resolvedConfig.windowConfiguration.getAppBounds())
- : newParentConfiguration.windowConfiguration.getAppBounds();
+ : parentAppBounds;
final int requestedOrientation = getRequestedConfigurationOrientation();
final boolean orientationRequested = requestedOrientation != ORIENTATION_UNDEFINED;
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 207707e..ac2c886 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -28,6 +28,7 @@
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
import static android.os.Process.INVALID_PID;
import static android.os.Process.INVALID_UID;
+import static android.os.Process.ROOT_UID;
import static android.os.Process.SYSTEM_UID;
import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
@@ -385,6 +386,10 @@
return BackgroundStartPrivileges.NONE;
case MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED:
// no explicit choice by the app - let us decide what to do
+ if (callingUid == ROOT_UID || callingUid == SYSTEM_UID) {
+ // root and system must always opt in explicitly
+ return BackgroundStartPrivileges.NONE;
+ }
if (callingPackage != null) {
// determine based on the calling/creating package
boolean changeEnabled = CompatChanges.isChangeEnabled(
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9dc9ad4..6c48e95 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1931,6 +1931,9 @@
if (td.getSystemBarsAppearance() == 0) {
td.setSystemBarsAppearance(atd.getSystemBarsAppearance());
}
+ if (td.getTopOpaqueSystemBarsAppearance() == 0 && r.fillsParent()) {
+ td.setTopOpaqueSystemBarsAppearance(atd.getSystemBarsAppearance());
+ }
if (td.getNavigationBarColor() == 0) {
td.setNavigationBarColor(atd.getNavigationBarColor());
td.setEnsureNavigationBarContrastWhenTransparent(
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d4bbe84..61022cc 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1346,10 +1346,13 @@
// In a multi-resumed environment, like in a freeform device, the top
// activity can be resumed, but it might not be the focused app.
- // Set focused app when top activity is resumed
- if (taskDisplayArea.inMultiWindowMode() && taskDisplayArea.mDisplayContent != null
- && taskDisplayArea.mDisplayContent.mFocusedApp != next) {
- taskDisplayArea.mDisplayContent.setFocusedApp(next);
+ // Set focused app when top activity is resumed. However, we shouldn't do it for a
+ // same task because it can break focused state. (e.g. activity embedding)
+ if (taskDisplayArea.inMultiWindowMode() && taskDisplayArea.mDisplayContent != null) {
+ final ActivityRecord focusedApp = taskDisplayArea.mDisplayContent.mFocusedApp;
+ if (focusedApp == null || focusedApp.getTask() != next.getTask()) {
+ taskDisplayArea.mDisplayContent.setFocusedApp(next);
+ }
}
ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Top activity "
+ "resumed %s", next);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 0bf1c88..a00b6fc4 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -1525,7 +1525,7 @@
InputChannel outInputChannel, InsetsState outInsetsState,
InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
float[] outSizeCompatScale) {
- outActiveControls.set(null);
+ outActiveControls.set(null, false /* copyControls */);
int[] appOp = new int[1];
final boolean isRoundedCornerOverlay = (attrs.privateFlags
& PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
@@ -1927,7 +1927,7 @@
displayContent.getInsetsStateController().updateAboveInsetsState(
false /* notifyInsetsChanged */);
- outInsetsState.set(win.getCompatInsetsState(), true /* copySources */);
+ win.fillInsetsState(outInsetsState, true /* copySources */);
getInsetsSourceControls(win, outActiveControls);
if (win.mLayoutAttached) {
@@ -2317,7 +2317,7 @@
InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls,
Bundle outBundle, WindowRelayoutResult outRelayoutResult) {
if (outActiveControls != null) {
- outActiveControls.set(null);
+ outActiveControls.set(null, false /* copyControls */);
}
int result = 0;
boolean configChanged = false;
@@ -2680,7 +2680,7 @@
}
if (outInsetsState != null) {
- outInsetsState.set(win.getCompatInsetsState(), true /* copySources */);
+ win.fillInsetsState(outInsetsState, true /* copySources */);
}
ProtoLog.v(WM_DEBUG_FOCUS, "Relayout of %s: focusMayChange=%b",
@@ -2743,25 +2743,14 @@
}
private void getInsetsSourceControls(WindowState win, InsetsSourceControl.Array outArray) {
- final InsetsSourceControl[] controls =
- win.getDisplayContent().getInsetsStateController().getControlsForDispatch(win);
- if (controls != null) {
- final int length = controls.length;
- final InsetsSourceControl[] outControls = new InsetsSourceControl[length];
- for (int i = 0; i < length; i++) {
- // We will leave the critical section before returning the leash to the client,
- // so we need to copy the leash to prevent others release the one that we are
- // about to return.
- if (controls[i] != null) {
- // This source control is an extra copy if the client is not local. By setting
- // PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of
- // SurfaceControl.writeToParcel.
- outControls[i] = new InsetsSourceControl(controls[i]);
- outControls[i].setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE);
- }
- }
- outArray.set(outControls);
- }
+ // We will leave the critical section before returning the leash to the client,
+ // so we need to copy the leash to prevent others release the one that we are
+ // about to return.
+ win.fillInsetsSourceControls(outArray, true /* copyControls */);
+ // This source control is an extra copy if the client is not local. By setting
+ // PARCELABLE_WRITE_RETURN_VALUE, the leash will be released at the end of
+ // SurfaceControl.writeToParcel.
+ outArray.setParcelableFlags(PARCELABLE_WRITE_RETURN_VALUE);
}
private void tryStartExitingAnimation(WindowState win, WindowStateAnimator winAnimator) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6953c60..d1efc27 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -434,7 +434,10 @@
/** @see #isLastConfigReportedToClient() */
private boolean mLastConfigReportedToClient;
- // TODO(b/339380439): Ensure to use the same object for IWindowSession#relayout
+ private final ClientWindowFrames mLastReportedFrames = new ClientWindowFrames();
+
+ private final InsetsState mLastReportedInsetsState = new InsetsState();
+
private final InsetsSourceControl.Array mLastReportedActiveControls =
new InsetsSourceControl.Array();
@@ -495,8 +498,6 @@
private final WindowFrames mWindowFrames = new WindowFrames();
- private final ClientWindowFrames mClientWindowFrames = new ClientWindowFrames();
-
/**
* List of rects where system gestures should be ignored.
*
@@ -3650,8 +3651,10 @@
outFrames.attachedFrame.scale(mInvGlobalScale);
}
}
-
outFrames.compatScale = getCompatScaleForClient();
+ if (mLastReportedFrames != outFrames) {
+ mLastReportedFrames.setTo(outFrames);
+ }
// Note: in the cases where the window is tied to an activity, we should not send a
// configuration update when the window has requested to be hidden. Doing so can lead to
@@ -3678,6 +3681,25 @@
mLastConfigReportedToClient = true;
}
+ void fillInsetsState(@NonNull InsetsState outInsetsState, boolean copySources) {
+ outInsetsState.set(getCompatInsetsState(), copySources);
+ if (outInsetsState != mLastReportedInsetsState) {
+ // No need to copy for the recorded.
+ mLastReportedInsetsState.set(outInsetsState, false /* copySources */);
+ }
+ }
+
+ void fillInsetsSourceControls(@NonNull InsetsSourceControl.Array outArray,
+ boolean copyControls) {
+ final InsetsSourceControl[] controls =
+ getDisplayContent().getInsetsStateController().getControlsForDispatch(this);
+ outArray.set(controls, copyControls);
+ if (outArray != mLastReportedActiveControls) {
+ // No need to copy for the recorded.
+ mLastReportedActiveControls.setTo(outArray, false /* copyControls */);
+ }
+ }
+
void reportResized() {
// If the activity is scheduled to relaunch, skip sending the resized to ViewRootImpl now
// since it will be destroyed anyway. This also prevents the client from receiving
@@ -3712,9 +3734,10 @@
final int prevRotation = mLastReportedConfiguration
.getMergedConfiguration().windowConfiguration.getRotation();
- fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration,
+ fillClientWindowFramesAndConfiguration(mLastReportedFrames, mLastReportedConfiguration,
mLastReportedActivityWindowInfo, true /* useLatestConfig */,
false /* relayoutVisible */);
+ fillInsetsState(mLastReportedInsetsState, false /* copySources */);
final boolean syncRedraw = shouldSendRedrawForSync();
final boolean syncWithBuffers = syncRedraw && shouldSyncWithBuffers();
final boolean reportDraw = syncRedraw || drawPending;
@@ -3734,8 +3757,8 @@
if (Flags.bundleClientTransactionFlag()) {
getProcess().scheduleClientTransactionItem(
- WindowStateResizeItem.obtain(mClient, mClientWindowFrames, reportDraw,
- mLastReportedConfiguration, getCompatInsetsState(), forceRelayout,
+ WindowStateResizeItem.obtain(mClient, mLastReportedFrames, reportDraw,
+ mLastReportedConfiguration, mLastReportedInsetsState, forceRelayout,
alwaysConsumeSystemBars, displayId,
syncWithBuffers ? mSyncSeqId : -1, isDragResizing,
mLastReportedActivityWindowInfo));
@@ -3743,8 +3766,8 @@
} else {
// TODO(b/301870955): cleanup after launch
try {
- mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
- getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
+ mClient.resized(mLastReportedFrames, reportDraw, mLastReportedConfiguration,
+ mLastReportedInsetsState, forceRelayout, alwaysConsumeSystemBars, displayId,
syncWithBuffers ? mSyncSeqId : -1, isDragResizing,
mLastReportedActivityWindowInfo);
onResizePostDispatched(drawPending, prevRotation, displayId);
@@ -3817,16 +3840,14 @@
if (mRemoved) {
return;
}
- final InsetsStateController stateController =
- getDisplayContent().getInsetsStateController();
- final InsetsState insetsState = getCompatInsetsState();
- mLastReportedActiveControls.set(stateController.getControlsForDispatch(this));
+ fillInsetsState(mLastReportedInsetsState, false /* copySources */);
+ fillInsetsSourceControls(mLastReportedActiveControls, false /* copyControls */);
if (Flags.insetsControlChangedItem()) {
getProcess().scheduleClientTransactionItem(WindowStateInsetsControlChangeItem.obtain(
- mClient, insetsState, mLastReportedActiveControls));
+ mClient, mLastReportedInsetsState, mLastReportedActiveControls));
} else {
try {
- mClient.insetsControlChanged(insetsState, mLastReportedActiveControls);
+ mClient.insetsControlChanged(mLastReportedInsetsState, mLastReportedActiveControls);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e);
}
diff --git a/services/core/jni/BroadcastRadio/convert.cpp b/services/core/jni/BroadcastRadio/convert.cpp
index ddbc535..e42f7f8 100644
--- a/services/core/jni/BroadcastRadio/convert.cpp
+++ b/services/core/jni/BroadcastRadio/convert.cpp
@@ -433,7 +433,7 @@
gjni.AmBandDescriptor.clazz, gjni.AmBandDescriptor.cstor,
region, config.type, config.lowerLimit, config.upperLimit, spacing, am.stereo));
} else {
- ALOGE("Unsupported band type: %d", config.type);
+ ALOGE("Unsupported band type: %d", static_cast<int>(config.type));
return nullptr;
}
}
@@ -451,7 +451,7 @@
return make_javaref(env, env->NewObject(
gjni.AmBandConfig.clazz, gjni.AmBandConfig.cstor, descriptor.get()));
} else {
- ALOGE("Unsupported band type: %d", config.type);
+ ALOGE("Unsupported band type: %d", static_cast<int>(config.type));
return nullptr;
}
}
@@ -539,9 +539,9 @@
item.clockValue.timezoneOffsetInMinutes);
break;
default:
- ALOGW("invalid metadata type %d", item.type);
+ ALOGW("invalid metadata type %d", static_cast<int>(item.type));
}
- ALOGE_IF(status != 0, "Failed inserting metadata %d (of type %d)", key, item.type);
+ ALOGE_IF(status != 0, "Failed inserting metadata %d (of type %d)", key, static_cast<int>(item.type));
}
return jMetadata;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index 7483b43..c7fd979 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -202,9 +202,9 @@
private String getDefaultSmsPackage() {
//TODO(b/319449037): Unflag the following change.
if (Flags.defaultSmsPersonalAppSuspensionFixEnabled()) {
- return SmsApplication.getDefaultSmsApplicationAsUser(
- mContext, /*updateIfNeeded=*/ false, mContext.getUser())
- .getPackageName();
+ ComponentName defaultSmsApp = SmsApplication.getDefaultSmsApplicationAsUser(
+ mContext, /*updateIfNeeded=*/ false, mContext.getUser());
+ return defaultSmsApp != null ? defaultSmsApp.getPackageName() : null;
} else {
return Telephony.Sms.getDefaultSmsPackage(mContext);
}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 927df8b..cfe4e17 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -761,9 +761,6 @@
}
}
- private static final long BINDER_CALLBACK_THROTTLE_MS = 10_100L;
- private long mBinderCallbackLast = -1;
-
private void run() {
TimingsTraceAndSlog t = new TimingsTraceAndSlog();
try {
@@ -968,14 +965,6 @@
Binder.setTransactionCallback(new IBinderCallback() {
@Override
public void onTransactionError(int pid, int code, int flags, int err) {
-
- final long now = SystemClock.uptimeMillis();
- if (now < mBinderCallbackLast + BINDER_CALLBACK_THROTTLE_MS) {
- Slog.d(TAG, "Too many transaction errors, throttling freezer binder callback.");
- return;
- }
- mBinderCallbackLast = now;
- Slog.wtfStack(TAG, "Binder Transaction Error");
mActivityManagerService.frozenBinderTransactionDetected(pid, code, flags, err);
}
});
diff --git a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
index b0c7073..3bdcd9b 100644
--- a/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
+++ b/services/permission/java/com/android/server/permission/access/appop/AppOpService.kt
@@ -18,6 +18,7 @@
import android.app.AppOpsManager
import android.companion.virtual.VirtualDeviceManager
+import android.os.Binder
import android.os.Handler
import android.os.UserHandle
import android.permission.PermissionManager
@@ -250,7 +251,9 @@
) {
Slog.w(
LOG_TAG,
- "Cannot set UID mode for runtime permission app op, uid = $uid," +
+ "Cannot set UID mode for runtime permission app op, " +
+ " callingUid = ${Binder.getCallingUid()}, " +
+ " uid = $uid," +
" code = ${AppOpsManager.opToName(code)}," +
" mode = ${AppOpsManager.modeToName(mode)}",
RuntimeException()
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
index 8db896b..bba4c8d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayBrightnessStateTest.java
@@ -58,6 +58,7 @@
.setShouldUseAutoBrightness(shouldUseAutoBrightness)
.setShouldUpdateScreenBrightnessSetting(shouldUpdateScreenBrightnessSetting)
.setBrightnessAdjustmentFlag(brightnessAdjustmentFlag)
+ .setIsUserInitiatedChange(true)
.build();
assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA);
@@ -109,7 +110,9 @@
.append("\n mBrightnessEvent:")
.append(Objects.toString(displayBrightnessState.getBrightnessEvent(), "null"))
.append("\n mBrightnessAdjustmentFlag:")
- .append(displayBrightnessState.getBrightnessAdjustmentFlag());
+ .append(displayBrightnessState.getBrightnessAdjustmentFlag())
+ .append("\n mIsUserInitiatedChange:")
+ .append(displayBrightnessState.isUserInitiatedChange());
return sb.toString();
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 12050e1..01ff35f 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -1142,6 +1142,20 @@
}
@Test
+ public void test_createLocalExternalDisplay_displayManagementEnabled_doesNotCrash()
+ throws Exception {
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.info.isInternal = false;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ when(mSurfaceControlProxy.getDesiredDisplayModeSpecs(display.token)).thenReturn(null);
+ mInjector.getTransmitter().sendHotplug(display, /* connected */ true);
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ }
+
+ @Test
public void test_createLocalExternalDisplay_displayManagementEnabled_shouldHaveDefaultGroup()
throws Exception {
FakeDisplay display = new FakeDisplay(PORT_A);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
index c5105e7..323ef6a 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java
@@ -121,7 +121,8 @@
any(StrategySelectionRequest.class))).thenReturn(displayBrightnessStrategy);
mDisplayBrightnessController.updateBrightness(displayPowerRequest, targetDisplayState);
verify(displayBrightnessStrategy).updateBrightness(
- eq(new StrategyExecutionRequest(displayPowerRequest, DEFAULT_BRIGHTNESS)));
+ eq(new StrategyExecutionRequest(displayPowerRequest, DEFAULT_BRIGHTNESS,
+ /* userSetBrightnessChanged= */ false)));
assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategy(),
displayBrightnessStrategy);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
index 7a6a911..4d5ff55 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java
@@ -115,7 +115,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mAutoBrightnessFallbackStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
index afb5a5c..8a33f34 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java
@@ -513,9 +513,11 @@
.setBrightnessEvent(brightnessEvent)
.setBrightnessAdjustmentFlag(BrightnessReason.ADJUSTMENT_AUTO)
.setShouldUpdateScreenBrightnessSetting(true)
+ .setIsUserInitiatedChange(true)
.build();
DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy
- .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f));
+ .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f,
+ /* userSetBrightnessChanged= */ true));
assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState);
}
@@ -560,9 +562,11 @@
.setBrightnessEvent(brightnessEvent)
.setBrightnessAdjustmentFlag(BrightnessReason.ADJUSTMENT_AUTO_TEMP)
.setShouldUpdateScreenBrightnessSetting(true)
+ .setIsUserInitiatedChange(true)
.build();
DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy
- .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f));
+ .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f,
+ /* userSetBrightnessChanged= */ true));
assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
index 47f1746..3534325 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java
@@ -60,7 +60,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mBoostBrightnessStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
index 9246780..bd6d8be 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java
@@ -57,7 +57,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mDozeBrightnessModeStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
index c4767ae..4cae35d 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java
@@ -57,10 +57,12 @@
.setSdrBrightness(currentBrightness)
.setDisplayBrightnessStrategyName(mFallbackBrightnessStrategy.getName())
.setShouldUpdateScreenBrightnessSetting(true)
+ .setIsUserInitiatedChange(true)
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mFallbackBrightnessStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, currentBrightness));
+ new StrategyExecutionRequest(displayPowerRequest, currentBrightness,
+ /* userSetBrightnessChanged= */ true));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
index 682c9cc..fdaf8f6 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java
@@ -61,7 +61,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mFollowerBrightnessStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(expectedDisplayBrightnessState, updatedDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
index ccf6309..e3c2760 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java
@@ -72,7 +72,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mOffloadBrightnessStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT, mOffloadBrightnessStrategy
.getOffloadScreenBrightness(), 0.0f);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
index 8e7b463..ebe407b 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java
@@ -60,7 +60,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mOverrideBrightnessStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
index e799d0e..4bad569 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java
@@ -58,7 +58,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mScreenOffBrightnessModeStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
index aaada41..5410a20 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java
@@ -60,7 +60,8 @@
.build();
DisplayBrightnessState updatedDisplayBrightnessState =
mTemporaryBrightnessStrategy.updateBrightness(
- new StrategyExecutionRequest(displayPowerRequest, 0.2f));
+ new StrategyExecutionRequest(displayPowerRequest, 0.2f,
+ /* userSetBrightnessChanged= */ false));
assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState);
}
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
index cf6146f..34c6ba9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/AppRequestObserverTest.kt
@@ -117,11 +117,11 @@
BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), RenderVote(60f, 90f)),
PREFERRED_REFRESH_RATE(false, 0, 60f, 0f, 0f,
BaseModeRefreshRateVote(60f), SizeVote(1000, 1000, 1000, 1000), null),
- PREFERRED_REFRESH_RATE_IGNORED(true, 0, 60f, 0f, 0f,
- null, null, null),
+ PREFERRED_REFRESH_RATE_IGNORE_BASE_MODE_CONVERSION(true, 0, 60f, 0f, 0f,
+ RequestedRefreshRateVote(60f), null, null),
PREFERRED_REFRESH_RATE_INVALID(false, 0, 25f, 0f, 0f,
null, null, null),
SYNTHETIC_MODE(false, 99, 0f, 0f, 0f,
- RenderVote(45f, 45f), SizeVote(1000, 1000, 1000, 1000), null),
+ RequestedRefreshRateVote(45f), SizeVote(1000, 1000, 1000, 1000), null),
}
}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/RequestedRefreshRateVoteTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/RequestedRefreshRateVoteTest.kt
new file mode 100644
index 0000000..dbe9e4a
--- /dev/null
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/RequestedRefreshRateVoteTest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display.mode
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class RequestedRefreshRateVoteTest {
+
+ @Test
+ fun `updates requestedRefreshRates`() {
+ val refreshRate = 90f
+ val vote = RequestedRefreshRateVote(refreshRate)
+ val summary = createVotesSummary()
+
+ vote.updateSummary(summary)
+
+ assertThat(summary.requestedRefreshRates).hasSize(1)
+ assertThat(summary.requestedRefreshRates).contains(refreshRate)
+ }
+
+ @Test
+ fun `updates requestedRefreshRates with multiple refresh rates`() {
+ val refreshRate1 = 90f
+ val vote1 = RequestedRefreshRateVote(refreshRate1)
+
+ val refreshRate2 = 60f
+ val vote2 = RequestedRefreshRateVote(refreshRate2)
+
+ val summary = createVotesSummary()
+
+ vote1.updateSummary(summary)
+ vote2.updateSummary(summary)
+
+ assertThat(summary.requestedRefreshRates).hasSize(2)
+ assertThat(summary.requestedRefreshRates).containsExactly(refreshRate1, refreshRate2)
+ }
+}
\ No newline at end of file
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
index 5da1bb6..dd5e1be 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/VoteSummaryTest.kt
@@ -152,13 +152,51 @@
assertThat(result.map { it.modeId }).containsExactlyElementsIn(testCase.expectedModeIds)
}
+
+ @Test
+ fun `summary invalid if has requestedRefreshRate less than minRenederRate`() {
+ val summary = createSummary()
+ summary.requestedRefreshRates = setOf(30f, 90f)
+ summary.minRenderFrameRate = 60f
+ summary.maxRenderFrameRate = 120f
+
+ val result = summary.filterModes(arrayOf(createMode(1, 90f, 90f)))
+
+ assertThat(result).isEmpty()
+ }
+
+ @Test
+ fun `summary invalid if has requestedRefreshRate more than maxRenderFrameRate`() {
+ val summary = createSummary()
+ summary.requestedRefreshRates = setOf(60f, 240f)
+ summary.minRenderFrameRate = 60f
+ summary.maxRenderFrameRate = 120f
+
+ val result = summary.filterModes(arrayOf(createMode(1, 90f, 90f)))
+
+ assertThat(result).isEmpty()
+ }
+
+ @Test
+ fun `summary valid if all requestedRefreshRates inside render rate limits`() {
+ val summary = createSummary()
+ summary.requestedRefreshRates = setOf(60f, 90f)
+ summary.minRenderFrameRate = 60f
+ summary.maxRenderFrameRate = 120f
+
+ val result = summary.filterModes(arrayOf(createMode(1, 90f, 90f)))
+
+ assertThat(result).hasSize(1)
+ }
}
+
+
private fun createMode(modeId: Int, refreshRate: Float, vsyncRate: Float): Display.Mode {
return Display.Mode(modeId, 600, 800, refreshRate, vsyncRate, false,
FloatArray(0), IntArray(0))
}
-private fun createSummary(supportedModesVoteEnabled: Boolean): VoteSummary {
+private fun createSummary(supportedModesVoteEnabled: Boolean = false): VoteSummary {
val summary = createVotesSummary(supportedModesVoteEnabled = supportedModesVoteEnabled)
summary.width = 600
summary.height = 800
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 0532e04..2a67029 100644
--- a/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/trust/TrustManagerServiceTest.java
@@ -16,6 +16,8 @@
package com.android.server.trust;
+import static android.service.trust.TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE;
+
import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
@@ -26,12 +28,22 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED;
+import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import static java.util.Collections.singleton;
+
import android.Manifest;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.AlarmManager;
import android.app.IActivityManager;
import android.app.admin.DevicePolicyManager;
import android.app.trust.ITrustListener;
@@ -65,15 +77,22 @@
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.security.KeyStoreAuthorization;
+import android.service.trust.GrantTrustResult;
+import android.service.trust.ITrustAgentService;
+import android.service.trust.ITrustAgentServiceCallback;
import android.service.trust.TrustAgentService;
import android.testing.TestableContext;
+import android.util.Log;
import android.view.IWindowManager;
import android.view.WindowManagerGlobal;
import androidx.test.core.app.ApplicationProvider;
+import com.android.internal.infra.AndroidFuture;
import com.android.internal.widget.LockPatternUtils;
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker;
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.StrongAuthFlags;
+import com.android.internal.widget.LockSettingsInternal;
import com.android.modules.utils.testing.ExtendedMockitoRule;
import com.android.server.LocalServices;
import com.android.server.SystemService;
@@ -81,15 +100,19 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
+import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class TrustManagerServiceTest {
@@ -115,21 +138,28 @@
private static final int PROFILE_USER_ID = 70;
private static final long[] PARENT_BIOMETRIC_SIDS = new long[] { 600L, 601L };
private static final long[] PROFILE_BIOMETRIC_SIDS = new long[] { 700L, 701L };
+ private static final long RENEWABLE_TRUST_DURATION = 10000L;
+ private static final String GRANT_TRUST_MESSAGE = "granted";
+ private static final String TAG = "TrustManagerServiceTest";
private final ArrayList<ResolveInfo> mTrustAgentResolveInfoList = new ArrayList<>();
private final ArrayList<ComponentName> mKnownTrustAgents = new ArrayList<>();
private final ArrayList<ComponentName> mEnabledTrustAgents = new ArrayList<>();
+ private final Map<ComponentName, ITrustAgentService.Stub> mMockTrustAgents = new HashMap<>();
private @Mock ActivityManager mActivityManager;
+ private @Mock AlarmManager mAlarmManager;
private @Mock BiometricManager mBiometricManager;
private @Mock DevicePolicyManager mDevicePolicyManager;
private @Mock FaceManager mFaceManager;
private @Mock FingerprintManager mFingerprintManager;
private @Mock KeyStoreAuthorization mKeyStoreAuthorization;
private @Mock LockPatternUtils mLockPatternUtils;
+ private @Mock LockSettingsInternal mLockSettingsInternal;
private @Mock PackageManager mPackageManager;
private @Mock UserManager mUserManager;
private @Mock IWindowManager mWindowManager;
+ private @Mock ITrustListener mTrustListener;
private HandlerThread mHandlerThread;
private TrustManagerService mService;
@@ -158,6 +188,9 @@
return null;
}).when(mLockPatternUtils).setEnabledTrustAgents(any(), eq(TEST_USER_ID));
+ LocalServices.removeServiceForTest(LockSettingsInternal.class);
+ LocalServices.addService(LockSettingsInternal.class, mLockSettingsInternal);
+
ArgumentMatcher<Intent> trustAgentIntentMatcher = new ArgumentMatcher<Intent>() {
@Override
public boolean matches(Intent argument) {
@@ -176,6 +209,7 @@
when(mWindowManager.isKeyguardLocked()).thenReturn(true);
mMockContext.addMockSystemService(ActivityManager.class, mActivityManager);
+ mMockContext.addMockSystemService(AlarmManager.class, mAlarmManager);
mMockContext.addMockSystemService(BiometricManager.class, mBiometricManager);
mMockContext.addMockSystemService(FaceManager.class, mFaceManager);
mMockContext.addMockSystemService(FingerprintManager.class, mFingerprintManager);
@@ -197,6 +231,7 @@
verify(() -> ServiceManager.addService(eq(Context.TRUST_SERVICE),
binderArgumentCaptor.capture(), anyBoolean(), anyInt()));
mTrustManager = ITrustManager.Stub.asInterface(binderArgumentCaptor.getValue());
+ mTrustManager.registerTrustListener(mTrustListener);
}
private class MockInjector extends TrustManagerService.Injector {
@@ -215,6 +250,11 @@
}
@Override
+ AlarmManager getAlarmManager() {
+ return mAlarmManager;
+ }
+
+ @Override
Looper getLooper() {
return mHandlerThread.getLooper();
}
@@ -367,12 +407,10 @@
@Test
public void reportEnabledTrustAgentsChangedInformsListener() throws RemoteException {
- final ITrustListener trustListener = mock(ITrustListener.class);
- mTrustManager.registerTrustListener(trustListener);
mService.waitForIdle();
mTrustManager.reportEnabledTrustAgentsChanged(TEST_USER_ID);
mService.waitForIdle();
- verify(trustListener).onEnabledTrustAgentsChanged(TEST_USER_ID);
+ verify(mTrustListener).onEnabledTrustAgentsChanged(TEST_USER_ID);
}
// Tests that when the device is locked for a managed profile with a *unified* challenge, the
@@ -416,6 +454,169 @@
}
@Test
+ public void testSuccessfulUnlock_bindsTrustAgent() throws Exception {
+ when(mUserManager.getAliveUsers())
+ .thenReturn(List.of(new UserInfo(TEST_USER_ID, "test", UserInfo.FLAG_FULL)));
+ ComponentName trustAgentName =
+ ComponentName.unflattenFromString("com.android/.SystemTrustAgent");
+ ITrustAgentService trustAgentService =
+ setUpTrustAgentWithStrongAuthRequired(
+ trustAgentName, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+
+ attemptSuccessfulUnlock(TEST_USER_ID);
+ mService.waitForIdle();
+
+ assertThat(getCallback(trustAgentService)).isNotNull();
+ }
+
+ @Test
+ public void testSuccessfulUnlock_notifiesTrustAgent() throws Exception {
+ ComponentName trustAgentName =
+ ComponentName.unflattenFromString("com.android/.SystemTrustAgent");
+ ITrustAgentService trustAgentService =
+ setUpTrustAgentWithStrongAuthRequired(
+ trustAgentName, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+
+ attemptSuccessfulUnlock(TEST_USER_ID);
+ mService.waitForIdle();
+
+ verify(trustAgentService).onUnlockAttempt(/* successful= */ true);
+ }
+
+ @Test
+ public void testSuccessfulUnlock_notifiesTrustListenerOfChangeInManagedTrust()
+ throws Exception {
+ ComponentName trustAgentName =
+ ComponentName.unflattenFromString("com.android/.SystemTrustAgent");
+ setUpTrustAgentWithStrongAuthRequired(trustAgentName, STRONG_AUTH_NOT_REQUIRED);
+ mService.waitForIdle();
+ Mockito.reset(mTrustListener);
+
+ attemptSuccessfulUnlock(TEST_USER_ID);
+ mService.waitForIdle();
+
+ verify(mTrustListener).onTrustManagedChanged(false, TEST_USER_ID);
+ }
+
+ @Test
+ @Ignore("TODO: b/340891566 - Trustagent always refreshes trustable timer for user 0 on unlock")
+ public void testSuccessfulUnlock_refreshesTrustableTimers() throws Exception {
+ ComponentName trustAgentName =
+ ComponentName.unflattenFromString("com.android/.SystemTrustAgent");
+ ITrustAgentService trustAgent =
+ setUpTrustAgentWithStrongAuthRequired(trustAgentName, STRONG_AUTH_NOT_REQUIRED);
+ setUpRenewableTrust(trustAgent);
+
+ attemptSuccessfulUnlock(TEST_USER_ID);
+ mService.waitForIdle();
+
+ // Idle and hard timeout alarms for first renewable trust granted
+ // Idle timeout alarm refresh for second renewable trust granted
+ // Idle and hard timeout alarms refresh for last report
+ verify(mAlarmManager, times(3))
+ .setExact(
+ eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
+ anyLong(),
+ anyString(),
+ any(AlarmManager.OnAlarmListener.class),
+ any(Handler.class));
+ }
+
+ @Test
+ public void testFailedUnlock_doesNotBindTrustAgent() throws Exception {
+ ComponentName trustAgentName =
+ ComponentName.unflattenFromString("com.android/.SystemTrustAgent");
+ ITrustAgentService trustAgentService =
+ setUpTrustAgentWithStrongAuthRequired(
+ trustAgentName, STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN);
+
+ attemptFailedUnlock(TEST_USER_ID);
+ mService.waitForIdle();
+
+ verify(trustAgentService, never()).setCallback(any());
+ }
+
+ @Test
+ public void testFailedUnlock_notifiesTrustAgent() throws Exception {
+ ComponentName trustAgentName =
+ ComponentName.unflattenFromString("com.android/.SystemTrustAgent");
+ ITrustAgentService trustAgentService =
+ setUpTrustAgentWithStrongAuthRequired(trustAgentName, STRONG_AUTH_NOT_REQUIRED);
+
+ attemptFailedUnlock(TEST_USER_ID);
+ mService.waitForIdle();
+
+ verify(trustAgentService).onUnlockAttempt(/* successful= */ false);
+ }
+
+ @Test
+ public void testFailedUnlock_doesNotNotifyTrustListenerOfChangeInManagedTrust()
+ throws Exception {
+ ComponentName trustAgentName =
+ ComponentName.unflattenFromString("com.android/.SystemTrustAgent");
+ setUpTrustAgentWithStrongAuthRequired(trustAgentName, STRONG_AUTH_NOT_REQUIRED);
+ Mockito.reset(mTrustListener);
+
+ attemptFailedUnlock(TEST_USER_ID);
+ mService.waitForIdle();
+
+ verify(mTrustListener, never()).onTrustManagedChanged(anyBoolean(), anyInt());
+ }
+
+ private void setUpRenewableTrust(ITrustAgentService trustAgent) throws RemoteException {
+ ITrustAgentServiceCallback callback = getCallback(trustAgent);
+ callback.setManagingTrust(true);
+ mService.waitForIdle();
+ attemptSuccessfulUnlock(TEST_USER_ID);
+ mService.waitForIdle();
+ when(mWindowManager.isKeyguardLocked()).thenReturn(false);
+ grantRenewableTrust(callback);
+ }
+
+ private ITrustAgentService setUpTrustAgentWithStrongAuthRequired(
+ ComponentName agentName, @StrongAuthFlags int strongAuthFlags) throws Exception {
+ doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(TEST_USER_ID);
+ addTrustAgent(agentName, true);
+ mLockPatternUtils.setKnownTrustAgents(singleton(agentName), TEST_USER_ID);
+ mLockPatternUtils.setEnabledTrustAgents(singleton(agentName), TEST_USER_ID);
+ when(mUserManager.isUserUnlockingOrUnlocked(TEST_USER_ID)).thenReturn(true);
+ setupStrongAuthTracker(strongAuthFlags, false);
+ mService.waitForIdle();
+ return getOrCreateMockTrustAgent(agentName);
+ }
+
+ private void attemptSuccessfulUnlock(int userId) throws RemoteException {
+ mTrustManager.reportUnlockAttempt(/* successful= */ true, userId);
+ }
+
+ private void attemptFailedUnlock(int userId) throws RemoteException {
+ mTrustManager.reportUnlockAttempt(/* successful= */ false, userId);
+ }
+
+ private void grantRenewableTrust(ITrustAgentServiceCallback callback) throws RemoteException {
+ Log.i(TAG, "Granting trust");
+ AndroidFuture<GrantTrustResult> future = new AndroidFuture<>();
+ callback.grantTrust(
+ GRANT_TRUST_MESSAGE,
+ RENEWABLE_TRUST_DURATION,
+ FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE,
+ future);
+ mService.waitForIdle();
+ }
+
+ /**
+ * Retrieve the ITrustAgentServiceCallback attached to a TrustAgentService after it has been
+ * bound to by the TrustManagerService. Will fail if no binding was established.
+ */
+ private ITrustAgentServiceCallback getCallback(ITrustAgentService trustAgentService)
+ throws RemoteException {
+ ArgumentCaptor<ITrustAgentServiceCallback> callbackCaptor =
+ ArgumentCaptor.forClass(ITrustAgentServiceCallback.class);
+ verify(trustAgentService).setCallback(callbackCaptor.capture());
+ return callbackCaptor.getValue();
+ }
+
+ @Test
@RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS_V2)
public void testKeystoreWeakUnlockEnabled_whenWeakFingerprintIsSetupAndAllowed()
throws Exception {
@@ -637,6 +838,20 @@
ResolveInfo resolveInfo = new ResolveInfo();
resolveInfo.serviceInfo = serviceInfo;
mTrustAgentResolveInfoList.add(resolveInfo);
+ ITrustAgentService.Stub mockService = getOrCreateMockTrustAgent(agentComponentName);
+ mMockContext.addMockService(agentComponentName, mockService);
+ mMockTrustAgents.put(agentComponentName, mockService);
+ }
+
+ private ITrustAgentService.Stub getOrCreateMockTrustAgent(ComponentName agentComponentName) {
+ return mMockTrustAgents.computeIfAbsent(
+ agentComponentName,
+ (componentName) -> {
+ ITrustAgentService.Stub mockTrustAgent = mock(ITrustAgentService.Stub.class);
+ when(mockTrustAgent.queryLocalInterface(anyString()))
+ .thenReturn(mockTrustAgent);
+ return mockTrustAgent;
+ });
}
private void bootService() {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index c4946f0..8914696 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -432,7 +432,6 @@
}
@Test
- @RequiresFlagsEnabled(Flags.FLAG_RESETTABLE_DYNAMIC_PROPERTIES)
public void binderDied_resetA11yServiceInfo() {
final int flag = AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS;
setServiceBinding(COMPONENT_NAME);
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
index 52b33db..d4f0d5a 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
@@ -593,6 +593,12 @@
}
@Override
+ public void onMagnificationSystemUIConnectionChanged(boolean connected)
+ throws RemoteException {
+
+ }
+
+ @Override
public void onMagnificationChanged(int displayId, Region region, MagnificationConfig config)
throws RemoteException {
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
index 87fe6cf..0de5807 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationConnectionManagerTest.java
@@ -59,6 +59,7 @@
import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.FlakyTest;
+import com.android.compatibility.common.util.TestUtils;
import com.android.internal.util.test.FakeSettingsProvider;
import com.android.server.LocalServices;
import com.android.server.accessibility.AccessibilityTraceManager;
@@ -76,6 +77,7 @@
*/
public class MagnificationConnectionManagerTest {
+ private static final int WAIT_CONNECTION_TIMEOUT_SECOND = 1;
private static final int CURRENT_USER_ID = UserHandle.USER_SYSTEM;
private static final int SERVICE_ID = 1;
@@ -115,17 +117,19 @@
private void stubSetConnection(boolean needDelay) {
doAnswer((InvocationOnMock invocation) -> {
final boolean connect = (Boolean) invocation.getArguments()[0];
- // Simulates setConnection() called by another process.
+ // Use post to simulate setConnection() called by another process.
+ final Context context = ApplicationProvider.getApplicationContext();
if (needDelay) {
- final Context context = ApplicationProvider.getApplicationContext();
context.getMainThreadHandler().postDelayed(
() -> {
mMagnificationConnectionManager.setConnection(
connect ? mMockConnection.getConnection() : null);
}, 10);
} else {
- mMagnificationConnectionManager.setConnection(
- connect ? mMockConnection.getConnection() : null);
+ context.getMainThreadHandler().post(() -> {
+ mMagnificationConnectionManager.setConnection(
+ connect ? mMockConnection.getConnection() : null);
+ });
}
return true;
}).when(mMockStatusBarManagerInternal).requestMagnificationConnection(anyBoolean());
@@ -629,9 +633,10 @@
}
@Test
- public void isConnected_requestConnection_expectedValue() throws RemoteException {
+ public void isConnected_requestConnection_expectedValue() throws Exception {
mMagnificationConnectionManager.requestConnection(true);
- assertTrue(mMagnificationConnectionManager.isConnected());
+ TestUtils.waitUntil("connection is not ready", WAIT_CONNECTION_TIMEOUT_SECOND,
+ () -> mMagnificationConnectionManager.isConnected());
mMagnificationConnectionManager.requestConnection(false);
assertFalse(mMagnificationConnectionManager.isConnected());
diff --git a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
index e756082..758c84a 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AbsoluteVolumeBehaviorTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.AppOpsManager;
import android.content.Context;
import android.content.res.Resources;
import android.media.AudioDeviceAttributes;
@@ -37,6 +38,7 @@
import android.media.AudioSystem;
import android.media.IAudioDeviceVolumeDispatcher;
import android.media.VolumeInfo;
+import android.os.PermissionEnforcer;
import android.os.RemoteException;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -98,7 +100,8 @@
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
- mTestLooper.getLooper()) {
+ mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
+ mock(AudioServerPermissionProvider.class)) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
index 3623012..2cb02bd 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceVolumeManagerTest.java
@@ -23,12 +23,14 @@
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
+import android.app.AppOpsManager;
import android.content.Context;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.AudioSystem;
import android.media.VolumeInfo;
+import android.os.PermissionEnforcer;
import android.os.test.TestLooper;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
@@ -75,7 +77,8 @@
mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase();
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
- mTestLooper.getLooper()) {
+ mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
+ mock(AudioServerPermissionProvider.class)) {
@Override
public int getDeviceForStream(int stream) {
return AudioSystem.DEVICE_OUT_SPEAKER;
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java
new file mode 100644
index 0000000..8d772ad
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServerPermissionProviderTest.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.audio;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.media.permission.INativePermissionController;
+import com.android.media.permission.UidPackageState;
+import com.android.server.pm.pkg.PackageState;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Objects;
+
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public final class AudioServerPermissionProviderTest {
+
+ // Class under test
+ private AudioServerPermissionProvider mPermissionProvider;
+
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock public INativePermissionController mMockPc;
+
+ @Mock public PackageState mMockPackageStateOne_10000_one;
+ @Mock public PackageState mMockPackageStateTwo_10001_two;
+ @Mock public PackageState mMockPackageStateThree_10000_one;
+ @Mock public PackageState mMockPackageStateFour_10000_three;
+ @Mock public PackageState mMockPackageStateFive_10001_four;
+ @Mock public PackageState mMockPackageStateSix_10000_two;
+
+ public List<UidPackageState> mInitPackageListExpected;
+
+ // Argument matcher which matches that the state is equal even if the package names are out of
+ // order (since they are logically a set).
+ public static final class UidPackageStateMatcher implements ArgumentMatcher<UidPackageState> {
+ private final int mUid;
+ private final List<String> mSortedPackages;
+
+ public UidPackageStateMatcher(int uid, List<String> packageNames) {
+ mUid = uid;
+ if (packageNames != null) {
+ mSortedPackages = new ArrayList(packageNames);
+ Collections.sort(mSortedPackages);
+ } else {
+ mSortedPackages = null;
+ }
+ }
+
+ public UidPackageStateMatcher(UidPackageState toMatch) {
+ this(toMatch.uid, toMatch.packageNames);
+ }
+
+ @Override
+ public boolean matches(UidPackageState state) {
+ if (state == null) return false;
+ if (state.uid != mUid) return false;
+ if ((state.packageNames == null) != (mSortedPackages == null)) return false;
+ var copy = new ArrayList(state.packageNames);
+ Collections.sort(copy);
+ return mSortedPackages.equals(copy);
+ }
+
+ @Override
+ public String toString() {
+ return "Matcher for UidState with uid: " + mUid + ": " + mSortedPackages;
+ }
+ }
+
+ public static final class PackageStateListMatcher
+ implements ArgumentMatcher<List<UidPackageState>> {
+
+ private final List<UidPackageState> mToMatch;
+
+ public PackageStateListMatcher(List<UidPackageState> toMatch) {
+ mToMatch = Objects.requireNonNull(toMatch);
+ }
+
+ @Override
+ public boolean matches(List<UidPackageState> other) {
+ if (other == null) return false;
+ if (other.size() != mToMatch.size()) return false;
+ for (int i = 0; i < mToMatch.size(); i++) {
+ var matcher = new UidPackageStateMatcher(mToMatch.get(i));
+ if (!matcher.matches(other.get(i))) return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "Matcher for List<UidState> with uid: " + mToMatch;
+ }
+ }
+
+ @Before
+ public void setup() {
+ when(mMockPackageStateOne_10000_one.getAppId()).thenReturn(10000);
+ when(mMockPackageStateOne_10000_one.getPackageName()).thenReturn("com.package.one");
+
+ when(mMockPackageStateTwo_10001_two.getAppId()).thenReturn(10001);
+ when(mMockPackageStateTwo_10001_two.getPackageName()).thenReturn("com.package.two");
+
+ // Same state as the first is intentional, emulating multi-user
+ when(mMockPackageStateThree_10000_one.getAppId()).thenReturn(10000);
+ when(mMockPackageStateThree_10000_one.getPackageName()).thenReturn("com.package.one");
+
+ when(mMockPackageStateFour_10000_three.getAppId()).thenReturn(10000);
+ when(mMockPackageStateFour_10000_three.getPackageName()).thenReturn("com.package.three");
+
+ when(mMockPackageStateFive_10001_four.getAppId()).thenReturn(10001);
+ when(mMockPackageStateFive_10001_four.getPackageName()).thenReturn("com.package.four");
+
+ when(mMockPackageStateSix_10000_two.getAppId()).thenReturn(10000);
+ when(mMockPackageStateSix_10000_two.getPackageName()).thenReturn("com.package.two");
+ }
+
+ @Test
+ public void testInitialPackagePopulation() throws Exception {
+ var initPackageListData =
+ List.of(
+ mMockPackageStateOne_10000_one,
+ mMockPackageStateTwo_10001_two,
+ mMockPackageStateThree_10000_one,
+ mMockPackageStateFour_10000_three,
+ mMockPackageStateFive_10001_four,
+ mMockPackageStateSix_10000_two);
+ var expectedPackageList =
+ List.of(
+ createUidPackageState(
+ 10000,
+ List.of("com.package.one", "com.package.two", "com.package.three")),
+ createUidPackageState(
+ 10001, List.of("com.package.two", "com.package.four")));
+
+ mPermissionProvider = new AudioServerPermissionProvider(initPackageListData);
+ mPermissionProvider.onServiceStart(mMockPc);
+ verify(mMockPc)
+ .populatePackagesForUids(argThat(new PackageStateListMatcher(expectedPackageList)));
+ }
+
+ @Test
+ public void testOnModifyPackageState_whenNewUid() throws Exception {
+ // 10000: one | 10001: two
+ var initPackageListData =
+ List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two);
+ mPermissionProvider = new AudioServerPermissionProvider(initPackageListData);
+ mPermissionProvider.onServiceStart(mMockPc);
+
+ // new uid, including user component
+ mPermissionProvider.onModifyPackageState(1_10002, "com.package.new", false /* isRemove */);
+
+ verify(mMockPc)
+ .updatePackagesForUid(
+ argThat(new UidPackageStateMatcher(10002, List.of("com.package.new"))));
+ verify(mMockPc).updatePackagesForUid(any()); // exactly once
+ }
+
+ @Test
+ public void testOnModifyPackageState_whenRemoveUid() throws Exception {
+ // 10000: one | 10001: two
+ var initPackageListData =
+ List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two);
+ mPermissionProvider = new AudioServerPermissionProvider(initPackageListData);
+ mPermissionProvider.onServiceStart(mMockPc);
+
+ // Includes user-id
+ mPermissionProvider.onModifyPackageState(1_10000, "com.package.one", true /* isRemove */);
+
+ verify(mMockPc).updatePackagesForUid(argThat(new UidPackageStateMatcher(10000, List.of())));
+ verify(mMockPc).updatePackagesForUid(any()); // exactly once
+ }
+
+ @Test
+ public void testOnModifyPackageState_whenUpdatedUidAddition() throws Exception {
+ // 10000: one | 10001: two
+ var initPackageListData =
+ List.of(mMockPackageStateOne_10000_one, mMockPackageStateTwo_10001_two);
+ mPermissionProvider = new AudioServerPermissionProvider(initPackageListData);
+ mPermissionProvider.onServiceStart(mMockPc);
+
+ // Includes user-id
+ mPermissionProvider.onModifyPackageState(1_10000, "com.package.new", false /* isRemove */);
+
+ verify(mMockPc)
+ .updatePackagesForUid(
+ argThat(
+ new UidPackageStateMatcher(
+ 10000, List.of("com.package.one", "com.package.new"))));
+ verify(mMockPc).updatePackagesForUid(any()); // exactly once
+ }
+
+ @Test
+ public void testOnModifyPackageState_whenUpdateUidRemoval() throws Exception {
+ // 10000: one, two | 10001: two
+ var initPackageListData =
+ List.of(
+ mMockPackageStateOne_10000_one,
+ mMockPackageStateTwo_10001_two,
+ mMockPackageStateSix_10000_two);
+ mPermissionProvider = new AudioServerPermissionProvider(initPackageListData);
+ mPermissionProvider.onServiceStart(mMockPc);
+
+ // Includes user-id
+ mPermissionProvider.onModifyPackageState(1_10000, "com.package.one", true /* isRemove */);
+
+ verify(mMockPc)
+ .updatePackagesForUid(
+ argThat(
+ new UidPackageStateMatcher(
+ createUidPackageState(10000, List.of("com.package.two")))));
+ verify(mMockPc).updatePackagesForUid(any()); // exactly once
+ }
+
+ @Test
+ public void testOnServiceStart() throws Exception {
+ // 10000: one, two | 10001: two
+ var initPackageListData =
+ List.of(
+ mMockPackageStateOne_10000_one,
+ mMockPackageStateTwo_10001_two,
+ mMockPackageStateSix_10000_two);
+ mPermissionProvider = new AudioServerPermissionProvider(initPackageListData);
+ mPermissionProvider.onServiceStart(mMockPc);
+ mPermissionProvider.onModifyPackageState(1_10000, "com.package.one", true /* isRemove */);
+ verify(mMockPc)
+ .updatePackagesForUid(
+ argThat(new UidPackageStateMatcher(10000, List.of("com.package.two"))));
+
+ verify(mMockPc).updatePackagesForUid(any()); // exactly once
+ mPermissionProvider.onModifyPackageState(
+ 1_10000, "com.package.three", false /* isRemove */);
+ verify(mMockPc)
+ .updatePackagesForUid(
+ argThat(
+ new UidPackageStateMatcher(
+ 10000, List.of("com.package.two", "com.package.three"))));
+ verify(mMockPc, times(2)).updatePackagesForUid(any()); // exactly twice
+ // state is now 10000: two, three | 10001: two
+
+ // simulate restart of the service
+ mPermissionProvider.onServiceStart(null); // should handle null
+ var newMockPc = mock(INativePermissionController.class);
+ mPermissionProvider.onServiceStart(newMockPc);
+
+ var expectedPackageList =
+ List.of(
+ createUidPackageState(
+ 10000, List.of("com.package.two", "com.package.three")),
+ createUidPackageState(10001, List.of("com.package.two")));
+
+ verify(newMockPc)
+ .populatePackagesForUids(argThat(new PackageStateListMatcher(expectedPackageList)));
+
+ verify(newMockPc, never()).updatePackagesForUid(any());
+ // updates should still work after restart
+ mPermissionProvider.onModifyPackageState(10001, "com.package.four", false /* isRemove */);
+ verify(newMockPc)
+ .updatePackagesForUid(
+ argThat(
+ new UidPackageStateMatcher(
+ 10001, List.of("com.package.two", "com.package.four"))));
+ // exactly once
+ verify(newMockPc).updatePackagesForUid(any());
+ }
+
+ private static UidPackageState createUidPackageState(int uid, List<String> packages) {
+ var res = new UidPackageState();
+ res.uid = uid;
+ res.packageNames = packages;
+ return res;
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index 634877e..037c3c0 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -66,6 +66,7 @@
@Mock private AppOpsManager mMockAppOpsManager;
@Mock private AudioPolicyFacade mMockAudioPolicy;
@Mock private PermissionEnforcer mMockPermissionEnforcer;
+ @Mock private AudioServerPermissionProvider mMockPermissionProvider;
// the class being unit-tested here
private AudioService mAudioService;
@@ -86,7 +87,7 @@
.thenReturn(AppOpsManager.MODE_ALLOWED);
mAudioService = new AudioService(mContext, mSpyAudioSystem, mSpySystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy, null,
- mMockAppOpsManager, mMockPermissionEnforcer);
+ mMockAppOpsManager, mMockPermissionEnforcer, mMockPermissionProvider);
}
/**
diff --git a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
index 8dfcc18..27b552f 100644
--- a/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/DeviceVolumeBehaviorTest.java
@@ -22,11 +22,13 @@
import static org.mockito.Mockito.mock;
import android.annotation.NonNull;
+import android.app.AppOpsManager;
import android.content.Context;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.media.IDeviceVolumeBehaviorDispatcher;
+import android.os.PermissionEnforcer;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
@@ -75,7 +77,8 @@
mAudioVolumeGroupHelper = new AudioVolumeGroupHelperBase();
mAudioService = new AudioService(mContext, mAudioSystem, mSystemServer,
mSettingsAdapter, mAudioVolumeGroupHelper, mAudioPolicyMock,
- mTestLooper.getLooper());
+ mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
+ mock(AudioServerPermissionProvider.class));
mTestLooper.dispatchAll();
}
diff --git a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
index 23728db..8e34ee1 100644
--- a/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/VolumeHelperTest.java
@@ -40,6 +40,7 @@
import static android.view.KeyEvent.KEYCODE_VOLUME_UP;
import static com.android.media.audio.Flags.FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME;
+import static com.android.media.audio.Flags.FLAG_ABS_VOLUME_INDEX_FIX;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -132,9 +133,12 @@
@Mock
private PermissionEnforcer mMockPermissionEnforcer;
@Mock
+ private AudioServerPermissionProvider mMockPermissionProvider;
+ @Mock
private AudioVolumeGroupHelperBase mAudioVolumeGroupHelper;
- private final AudioPolicyFacade mFakeAudioPolicy = lookbackAudio -> false;
+ @Mock
+ private AudioPolicyFacade mMockAudioPolicy;
private AudioVolumeGroup mAudioMusicVolumeGroup;
@@ -153,9 +157,10 @@
SystemServerAdapter systemServer, SettingsAdapter settings,
AudioVolumeGroupHelperBase audioVolumeGroupHelper, AudioPolicyFacade audioPolicy,
@Nullable Looper looper, AppOpsManager appOps,
- @NonNull PermissionEnforcer enforcer) {
+ @NonNull PermissionEnforcer enforcer,
+ AudioServerPermissionProvider permissionProvider) {
super(context, audioSystem, systemServer, settings, audioVolumeGroupHelper,
- audioPolicy, looper, appOps, enforcer);
+ audioPolicy, looper, appOps, enforcer, permissionProvider);
}
public void setDeviceForStream(int stream, int device) {
@@ -209,8 +214,9 @@
mAm = mContext.getSystemService(AudioManager.class);
mAudioService = new MyAudioService(mContext, mSpyAudioSystem, mSpySystemServer,
- mSettingsAdapter, mAudioVolumeGroupHelper, mFakeAudioPolicy,
- mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer);
+ mSettingsAdapter, mAudioVolumeGroupHelper, mMockAudioPolicy,
+ mTestLooper.getLooper(), mMockAppOpsManager, mMockPermissionEnforcer,
+ mMockPermissionProvider);
mTestLooper.dispatchAll();
prepareAudioServiceState();
@@ -552,7 +558,7 @@
}
@Test
- @RequiresFlagsDisabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME)
+ @RequiresFlagsDisabled({FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME, FLAG_ABS_VOLUME_INDEX_FIX})
public void configurablePreScaleAbsoluteVolume_checkIndex() throws Exception {
final int minIndex = mAm.getStreamMinVolume(STREAM_MUSIC);
final int maxIndex = mAm.getStreamMaxVolume(STREAM_MUSIC);
@@ -607,6 +613,7 @@
@Test
@RequiresFlagsEnabled(FLAG_DISABLE_PRESCALE_ABSOLUTE_VOLUME)
+ @RequiresFlagsDisabled(FLAG_ABS_VOLUME_INDEX_FIX)
public void disablePreScaleAbsoluteVolume_checkIndex() throws Exception {
final int minIndex = mAm.getStreamMinVolume(STREAM_MUSIC);
final int maxIndex = mAm.getStreamMaxVolume(STREAM_MUSIC);
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
index 7dd1847..50cfa75 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerBackupRestoreTest.java
@@ -20,7 +20,6 @@
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
-import static org.junit.Assert.assertNotNull;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -52,6 +51,7 @@
import android.util.SparseArray;
import android.util.Xml;
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.android.internal.content.PackageMonitor;
@@ -70,6 +70,7 @@
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
@@ -95,21 +96,21 @@
private static final int DEFAULT_USER_ID = 0;
private static final int WORK_PROFILE_USER_ID = 10;
private static final int DEFAULT_UID = Binder.getCallingUid() + 100;
+ private static final int WORK_PROFILE_UID = Binder.getCallingUid() + 1000100;
private static final long DEFAULT_CREATION_TIME_MILLIS = 1000;
private static final Duration RETENTION_PERIOD = Duration.ofDays(3);
private static final LocaleList DEFAULT_LOCALES =
LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
private static final Map<String, LocalesInfo> DEFAULT_PACKAGE_LOCALES_INFO_MAP = Map.of(
DEFAULT_PACKAGE_NAME, new LocalesInfo(DEFAULT_LOCALE_TAGS, false));
- private static final SparseArray<LocaleManagerBackupHelper.StagedData> STAGE_DATA =
- new SparseArray<>();
+ private final SparseArray<File> mStagedDataFiles = new SparseArray<>();
+ private File mArchivedPackageFile;
private LocaleManagerBackupHelper mBackupHelper;
private long mCurrentTimeMillis;
+ private Context mContext = spy(ApplicationProvider.getApplicationContext());
@Mock
- private Context mMockContext;
- @Mock
private PackageManager mMockPackageManager;
@Mock
private LocaleManagerService mMockLocaleManagerService;
@@ -138,23 +139,28 @@
@Before
public void setUp() throws Exception {
- mMockContext = mock(Context.class);
mMockPackageManager = mock(PackageManager.class);
mMockLocaleManagerService = mock(LocaleManagerService.class);
mMockDelegateAppLocalePackages = mock(SharedPreferences.class);
mMockSpEditor = mock(SharedPreferences.Editor.class);
SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class);
- doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
+ doReturn(mMockPackageManager).when(mContext).getPackageManager();
doReturn(mMockSpEditor).when(mMockDelegateAppLocalePackages).edit();
HandlerThread broadcastHandlerThread = new HandlerThread(TAG,
Process.THREAD_PRIORITY_BACKGROUND);
broadcastHandlerThread.start();
- mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mMockContext,
- mMockLocaleManagerService, mMockPackageManager, mClock, STAGE_DATA,
- broadcastHandlerThread, mMockDelegateAppLocalePackages));
+ File file0 = new File(mContext.getCacheDir(), "file_user_0.txt");
+ File file10 = new File(mContext.getCacheDir(), "file_user_10.txt");
+ mStagedDataFiles.put(DEFAULT_USER_ID, file0);
+ mStagedDataFiles.put(WORK_PROFILE_USER_ID, file10);
+ mArchivedPackageFile = new File(mContext.getCacheDir(), "file_archived.txt");
+
+ mBackupHelper = spy(new ShadowLocaleManagerBackupHelper(mContext,
+ mMockLocaleManagerService, mMockPackageManager, mClock, broadcastHandlerThread,
+ mStagedDataFiles, mArchivedPackageFile, mMockDelegateAppLocalePackages));
doNothing().when(mBackupHelper).notifyBackupManager();
mUserMonitor = mBackupHelper.getUserMonitor();
@@ -165,7 +171,16 @@
@After
public void tearDown() throws Exception {
- STAGE_DATA.clear();
+ for (int i = 0; i < mStagedDataFiles.size(); i++) {
+ int userId = mStagedDataFiles.keyAt(i);
+ File file = mStagedDataFiles.get(userId);
+ SharedPreferences sp = mContext.getSharedPreferences(file, Context.MODE_PRIVATE);
+ sp.edit().clear().commit();
+ if (file.exists()) {
+ file.delete();
+ }
+ }
+ mStagedDataFiles.clear();
}
@Test
@@ -543,17 +558,21 @@
mPackageMonitor.onPackageAddedWithExtras(pkgNameA, DEFAULT_UID, bundle);
mPackageMonitor.onPackageAddedWithExtras(pkgNameB, DEFAULT_UID, bundle);
+ checkArchivedFileExists();
+
mBackupHelper.stageAndApplyRestoredPayload(out.toByteArray(), DEFAULT_USER_ID);
verifyNothingRestored();
setUpPackageInstalled(pkgNameA);
- mPackageMonitor.onPackageUpdateFinished(pkgNameA, DEFAULT_UID);
+ mBackupHelper.onPackageUpdateFinished(pkgNameA, DEFAULT_UID);
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameA, DEFAULT_USER_ID,
LocaleList.forLanguageTags(langTagsA), false, FrameworkStatsLog
.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+ checkArchivedFileExists();
+
mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameA, false, false);
@@ -565,11 +584,12 @@
setUpPackageInstalled(pkgNameB);
- mPackageMonitor.onPackageUpdateFinished(pkgNameB, DEFAULT_UID);
+ mBackupHelper.onPackageUpdateFinished(pkgNameB, DEFAULT_UID);
verify(mMockLocaleManagerService, times(1)).setApplicationLocales(pkgNameB, DEFAULT_USER_ID,
LocaleList.forLanguageTags(langTagsB), true, FrameworkStatsLog
.APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+ checkArchivedFileDoesNotExist();
mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, pkgNameB, true, false);
@@ -723,7 +743,7 @@
Intent intent = new Intent();
intent.setAction(Intent.ACTION_USER_REMOVED);
intent.putExtra(Intent.EXTRA_USER_HANDLE, DEFAULT_USER_ID);
- mUserMonitor.onReceive(mMockContext, intent);
+ mUserMonitor.onReceive(mContext, intent);
// Stage data should be removed only for DEFAULT_USER_ID.
checkStageDataDoesNotExist(DEFAULT_USER_ID);
@@ -732,6 +752,72 @@
}
@Test
+ public void testRestore_multipleProfile_restoresFromStage_ArchiveEnabled() throws Exception {
+ final ByteArrayOutputStream outDefault = new ByteArrayOutputStream();
+ writeTestPayload(outDefault, DEFAULT_PACKAGE_LOCALES_INFO_MAP);
+ final ByteArrayOutputStream outWorkProfile = new ByteArrayOutputStream();
+ String anotherPackage = "com.android.anotherapp";
+ String anotherLangTags = "mr,zh";
+ LocalesInfo localesInfo = new LocalesInfo(anotherLangTags, true);
+ HashMap<String, LocalesInfo> pkgLocalesMapWorkProfile = new HashMap<>();
+ pkgLocalesMapWorkProfile.put(anotherPackage, localesInfo);
+ writeTestPayload(outWorkProfile, pkgLocalesMapWorkProfile);
+ // DEFAULT_PACKAGE_NAME is NOT installed on the device.
+ setUpPackageNotInstalled(DEFAULT_PACKAGE_NAME);
+ setUpPackageNotInstalled(anotherPackage);
+ setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
+ setUpLocalesForPackage(anotherPackage, LocaleList.getEmptyLocaleList());
+ setUpPackageNamesForSp(new ArraySet<>());
+
+ Bundle bundle = new Bundle();
+ bundle.putBoolean(Intent.EXTRA_ARCHIVAL, true);
+ mPackageMonitor.onPackageAddedWithExtras(DEFAULT_PACKAGE_NAME, DEFAULT_UID, bundle);
+ mPackageMonitor.onPackageAddedWithExtras(anotherPackage, WORK_PROFILE_UID, bundle);
+
+ checkArchivedFileExists();
+
+ mBackupHelper.stageAndApplyRestoredPayload(outDefault.toByteArray(), DEFAULT_USER_ID);
+ mBackupHelper.stageAndApplyRestoredPayload(outWorkProfile.toByteArray(),
+ WORK_PROFILE_USER_ID);
+
+ verifyNothingRestored();
+ verifyStageDataForUser(DEFAULT_PACKAGE_LOCALES_INFO_MAP,
+ DEFAULT_CREATION_TIME_MILLIS, DEFAULT_USER_ID);
+ verifyStageDataForUser(pkgLocalesMapWorkProfile,
+ DEFAULT_CREATION_TIME_MILLIS, WORK_PROFILE_USER_ID);
+
+ setUpPackageInstalled(DEFAULT_PACKAGE_NAME);
+ mBackupHelper.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME, DEFAULT_UID);
+
+ verify(mMockLocaleManagerService, times(1)).setApplicationLocales(DEFAULT_PACKAGE_NAME,
+ DEFAULT_USER_ID,
+ LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS), false, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+ checkArchivedFileExists();
+ checkStageDataDoesNotExist(DEFAULT_USER_ID);
+
+ mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, DEFAULT_PACKAGE_NAME, false,
+ false);
+
+ verify(mMockSpEditor, times(0)).putStringSet(anyString(), any());
+
+ setUpPackageInstalled(anotherPackage);
+ mBackupHelper.onPackageUpdateFinished(anotherPackage, WORK_PROFILE_UID);
+
+ verify(mMockLocaleManagerService, times(1)).setApplicationLocales(anotherPackage,
+ WORK_PROFILE_USER_ID,
+ LocaleList.forLanguageTags(anotherLangTags), true, FrameworkStatsLog
+ .APPLICATION_LOCALES_CHANGED__CALLER__CALLER_BACKUP_RESTORE);
+ checkArchivedFileDoesNotExist();
+
+ mBackupHelper.persistLocalesModificationInfo(DEFAULT_USER_ID, anotherPackage, true, false);
+
+ verify(mMockSpEditor, times(1)).putStringSet(Integer.toString(DEFAULT_USER_ID),
+ new ArraySet<>(Arrays.asList(anotherPackage)));
+ checkStageDataDoesNotExist(WORK_PROFILE_USER_ID);
+ }
+
+ @Test
public void testPackageRemoved_noInfoInSp() throws Exception {
String pkgNameA = "com.android.myAppA";
String pkgNameB = "com.android.myAppB";
@@ -858,10 +944,22 @@
private void verifyStageDataForUser(Map<String, LocalesInfo> expectedPkgLocalesMap,
long expectedCreationTimeMillis, int userId) {
- LocaleManagerBackupHelper.StagedData stagedDataForUser = STAGE_DATA.get(userId);
- assertNotNull(stagedDataForUser);
- assertEquals(expectedCreationTimeMillis, stagedDataForUser.mCreationTimeMillis);
- verifyStageData(expectedPkgLocalesMap, stagedDataForUser.mPackageStates);
+ SharedPreferences sp = mContext.getSharedPreferences(mStagedDataFiles.get(userId),
+ Context.MODE_PRIVATE);
+ assertTrue(sp.getAll().size() > 0);
+ assertEquals(expectedCreationTimeMillis, sp.getLong("staged_data_time", -1));
+ verifyStageData(expectedPkgLocalesMap, sp);
+ }
+
+ private static void verifyStageData(Map<String, LocalesInfo> expectedPkgLocalesMap,
+ SharedPreferences sp) {
+ for (String pkg : expectedPkgLocalesMap.keySet()) {
+ assertTrue(!sp.getString(pkg, "").isEmpty());
+ String[] info = sp.getString(pkg, "").split(" s:");
+ assertEquals(expectedPkgLocalesMap.get(pkg).mLocales, info[0]);
+ assertEquals(expectedPkgLocalesMap.get(pkg).mSetFromDelegate,
+ Boolean.parseBoolean(info[1]));
+ }
}
private static void verifyStageData(Map<String, LocalesInfo> expectedPkgLocalesMap,
@@ -875,11 +973,19 @@
}
}
- private static void checkStageDataExists(int userId) {
- assertNotNull(STAGE_DATA.get(userId));
+ private void checkStageDataExists(int userId) {
+ assertTrue(mStagedDataFiles.get(userId) != null && mStagedDataFiles.get(userId).exists());
}
- private static void checkStageDataDoesNotExist(int userId) {
- assertNull(STAGE_DATA.get(userId));
+ private void checkStageDataDoesNotExist(int userId) {
+ assertTrue(mStagedDataFiles.get(userId) == null || !mStagedDataFiles.get(userId).exists());
}
-}
+
+ private void checkArchivedFileExists() {
+ assertTrue(mArchivedPackageFile.exists());
+ }
+
+ private void checkArchivedFileDoesNotExist() {
+ assertTrue(!mArchivedPackageFile.exists());
+ }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
index 9f7cbe3..b46902d 100644
--- a/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
+++ b/services/tests/servicestests/src/com/android/server/locales/ShadowLocaleManagerBackupHelper.java
@@ -22,6 +22,7 @@
import android.os.HandlerThread;
import android.util.SparseArray;
+import java.io.File;
import java.time.Clock;
/**
@@ -33,9 +34,9 @@
ShadowLocaleManagerBackupHelper(Context context,
LocaleManagerService localeManagerService,
PackageManager packageManager, Clock clock,
- SparseArray<LocaleManagerBackupHelper.StagedData> stagedData,
- HandlerThread broadcastHandlerThread, SharedPreferences delegateAppLocalePackages) {
- super(context, localeManagerService, packageManager, clock, stagedData,
- broadcastHandlerThread, delegateAppLocalePackages);
+ HandlerThread broadcastHandlerThread, SparseArray<File> stagedDataFiles,
+ File archivedPackagesFile, SharedPreferences delegateAppLocalePackages) {
+ super(context, localeManagerService, packageManager, clock, broadcastHandlerThread,
+ stagedDataFiles, archivedPackagesFile, delegateAppLocalePackages);
}
}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
index 64e6236..17b499e 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/RebootEscrowManagerTests.java
@@ -19,6 +19,7 @@
import static android.content.pm.UserInfo.FLAG_FULL;
import static android.content.pm.UserInfo.FLAG_PRIMARY;
import static android.content.pm.UserInfo.FLAG_PROFILE;
+import static android.content.pm.UserInfo.NO_PROFILE_GROUP_ID;
import static android.os.UserHandle.USER_SYSTEM;
import static com.android.internal.widget.LockSettingsInternal.ARM_REBOOT_ERROR_ESCROW_NOT_READY;
@@ -32,6 +33,7 @@
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyByte;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
@@ -65,6 +67,7 @@
import com.android.internal.widget.RebootEscrowListener;
import com.android.server.locksettings.ResumeOnRebootServiceProvider.ResumeOnRebootServiceConnection;
+import com.android.server.pm.UserManagerInternal;
import org.junit.Before;
import org.junit.Test;
@@ -107,6 +110,7 @@
private Context mContext;
private UserManager mUserManager;
+ private UserManagerInternal mUserManagerInternal;
private RebootEscrowManager.Callbacks mCallbacks;
private IRebootEscrow mRebootEscrow;
private ResumeOnRebootServiceConnection mServiceConnection;
@@ -126,13 +130,15 @@
long getCurrentTimeMillis();
void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount,
- int escrowDurationInSeconds, int vbmetaDigestStatus, int durationSinceBootComplete);
+ int escrowDurationInSeconds, int vbmetaDigestStatus,
+ int durationSinceBootComplete);
}
static class MockInjector extends RebootEscrowManager.Injector {
private final IRebootEscrow mRebootEscrow;
private final RebootEscrowProviderInterface mDefaultRebootEscrowProvider;
private final UserManager mUserManager;
+ private final UserManagerInternal mUserManagerInternal;
private final MockableRebootEscrowInjected mInjected;
private final RebootEscrowKeyStoreManager mKeyStoreManager;
private boolean mServerBased;
@@ -141,12 +147,16 @@
private Consumer<ConnectivityManager.NetworkCallback> mNetworkConsumer;
private boolean mWaitForInternet;
- MockInjector(Context context, UserManager userManager,
+ MockInjector(
+ Context context,
+ UserManager userManager,
+ UserManagerInternal userManagerInternal,
IRebootEscrow rebootEscrow,
RebootEscrowKeyStoreManager keyStoreManager,
LockSettingsStorageTestable storage,
MockableRebootEscrowInjected injected) {
- super(context, storage);
+ // TODO: change this
+ super(context, storage, userManagerInternal);
mRebootEscrow = rebootEscrow;
mServerBased = false;
mWaitForInternet = false;
@@ -159,16 +169,20 @@
};
mDefaultRebootEscrowProvider = new RebootEscrowProviderHalImpl(halInjector);
mUserManager = userManager;
+ mUserManagerInternal = userManagerInternal;
mKeyStoreManager = keyStoreManager;
mInjected = injected;
}
- MockInjector(Context context, UserManager userManager,
+ MockInjector(
+ Context context,
+ UserManager userManager,
+ UserManagerInternal userManagerInternal,
ResumeOnRebootServiceConnection serviceConnection,
RebootEscrowKeyStoreManager keyStoreManager,
LockSettingsStorageTestable storage,
MockableRebootEscrowInjected injected) {
- super(context, storage);
+ super(context, storage, userManagerInternal);
mRebootEscrow = null;
mServerBased = true;
mWaitForInternet = false;
@@ -187,6 +201,7 @@
mDefaultRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl(
storage, injector);
mUserManager = userManager;
+ mUserManagerInternal = userManagerInternal;
mKeyStoreManager = keyStoreManager;
mInjected = injected;
}
@@ -202,6 +217,11 @@
}
@Override
+ public UserManagerInternal getUserManagerInternal() {
+ return mUserManagerInternal;
+ }
+
+ @Override
public boolean serverBasedResumeOnReboot() {
return mServerBased;
}
@@ -289,8 +309,8 @@
@Override
public void reportMetric(boolean success, int errorCode, int serviceType, int attemptCount,
- int escrowDurationInSeconds, int vbmetaDigestStatus,
- int durationSinceBootComplete) {
+ int escrowDurationInSeconds, int vbmetaDigestStatus,
+ int durationSinceBootComplete) {
mInjected.reportMetric(success, errorCode, serviceType, attemptCount,
escrowDurationInSeconds, vbmetaDigestStatus, durationSinceBootComplete);
@@ -301,6 +321,7 @@
public void setUp_baseServices() throws Exception {
mContext = new ContextWrapper(InstrumentationRegistry.getContext());
mUserManager = mock(UserManager.class);
+ mUserManagerInternal = mock(UserManagerInternal.class);
mCallbacks = mock(RebootEscrowManager.Callbacks.class);
mRebootEscrow = mock(IRebootEscrow.class);
mServiceConnection = mock(ResumeOnRebootServiceConnection.class);
@@ -314,28 +335,43 @@
new File(InstrumentationRegistry.getContext().getFilesDir(), "locksettings"));
ArrayList<UserInfo> users = new ArrayList<>();
- users.add(new UserInfo(PRIMARY_USER_ID, "primary", FLAG_PRIMARY));
- users.add(new UserInfo(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE));
- users.add(new UserInfo(NONSECURE_SECONDARY_USER_ID, "non-secure", FLAG_FULL));
- users.add(new UserInfo(SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL));
+ users.add(createUser(PRIMARY_USER_ID, "primary", FLAG_PRIMARY, PRIMARY_USER_ID));
+ users.add(createUser(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE, PRIMARY_USER_ID));
+ users.add(
+ createUser(
+ NONSECURE_SECONDARY_USER_ID, "non-secure", FLAG_FULL, NO_PROFILE_GROUP_ID));
+ users.add(createUser(SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL, NO_PROFILE_GROUP_ID));
when(mUserManager.getUsers()).thenReturn(users);
when(mCallbacks.isUserSecure(PRIMARY_USER_ID)).thenReturn(true);
when(mCallbacks.isUserSecure(WORK_PROFILE_USER_ID)).thenReturn(true);
when(mCallbacks.isUserSecure(NONSECURE_SECONDARY_USER_ID)).thenReturn(false);
when(mCallbacks.isUserSecure(SECURE_SECONDARY_USER_ID)).thenReturn(true);
mInjected = mock(MockableRebootEscrowInjected.class);
- mMockInjector = new MockInjector(mContext, mUserManager, mRebootEscrow,
- mKeyStoreManager, mStorage, mInjected);
+ mMockInjector =
+ new MockInjector(
+ mContext,
+ mUserManager,
+ mUserManagerInternal,
+ mRebootEscrow,
+ mKeyStoreManager,
+ mStorage,
+ mInjected);
HandlerThread thread = new HandlerThread("RebootEscrowManagerTest");
thread.start();
mHandler = new Handler(thread.getLooper());
mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler);
-
}
private void setServerBasedRebootEscrowProvider() throws Exception {
- mMockInjector = new MockInjector(mContext, mUserManager, mServiceConnection,
- mKeyStoreManager, mStorage, mInjected);
+ mMockInjector =
+ new MockInjector(
+ mContext,
+ mUserManager,
+ mUserManagerInternal,
+ mServiceConnection,
+ mKeyStoreManager,
+ mStorage,
+ mInjected);
mService = new RebootEscrowManager(mMockInjector, mCallbacks, mStorage, mHandler);
}
@@ -352,6 +388,12 @@
waitForHandler();
}
+ private UserInfo createUser(int id, String name, int flag, int profileGroupId) {
+ UserInfo user = new UserInfo(id, name, flag);
+ when(mUserManagerInternal.getProfileParentId(eq(id))).thenReturn(profileGroupId);
+ return user;
+ }
+
@Test
public void prepareRebootEscrow_Success() throws Exception {
RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
@@ -559,6 +601,172 @@
}
@Test
+ public void loadRebootEscrowDataIfAvailable_noDataPrimaryUser_Failure() throws Exception {
+ setServerBasedRebootEscrowProvider();
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+
+ clearInvocations(mServiceConnection);
+
+ // escrow secondary user, don't escrow primary user
+ callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+ when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+ assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+ verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+
+ assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID));
+ assertFalse(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
+ assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+ // pretend reboot happens here
+ when(mInjected.getBootCount()).thenReturn(1);
+ ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+ ArgumentCaptor<Integer> metricsErrorCodeCaptor = ArgumentCaptor.forClass(Integer.class);
+ doNothing()
+ .when(mInjected)
+ .reportMetric(
+ metricsSuccessCaptor.capture(),
+ metricsErrorCodeCaptor.capture(),
+ eq(2) /* Server based */,
+ eq(1) /* attempt count */,
+ anyInt(),
+ eq(0) /* vbmeta status */,
+ anyInt());
+ mService.loadRebootEscrowDataIfAvailable(null);
+ verify(mServiceConnection, never()).unwrap(any(), anyLong());
+ verify(mCallbacks, never()).onRebootEscrowRestored(anyByte(), any(), anyInt());
+ assertFalse(metricsSuccessCaptor.getValue());
+ assertEquals(
+ Integer.valueOf(RebootEscrowManager.ERROR_NO_REBOOT_ESCROW_DATA),
+ metricsErrorCodeCaptor.getValue());
+ }
+
+ @Test
+ public void loadRebootEscrowDataIfAvailable_noDataSecondaryUser_Success() throws Exception {
+ setServerBasedRebootEscrowProvider();
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+
+ clearInvocations(mServiceConnection);
+
+ // Setup work profile with secondary user as parent.
+ ArrayList<UserInfo> users = new ArrayList<>();
+ users.add(createUser(PRIMARY_USER_ID, "primary", FLAG_PRIMARY, NO_PROFILE_GROUP_ID));
+ users.add(createUser(WORK_PROFILE_USER_ID, "work", FLAG_PROFILE, SECURE_SECONDARY_USER_ID));
+ users.add(
+ createUser(
+ SECURE_SECONDARY_USER_ID, "secure", FLAG_FULL, SECURE_SECONDARY_USER_ID));
+ when(mUserManager.getUsers()).thenReturn(users);
+
+ // escrow primary user and work profile, don't escrow secondary user
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+ callToRebootEscrowIfNeededAndWait(WORK_PROFILE_USER_ID);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+ when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+ assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+ verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+
+ assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
+ assertFalse(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID));
+ assertTrue(mStorage.hasRebootEscrow(WORK_PROFILE_USER_ID));
+ assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+ // pretend reboot happens here
+ when(mInjected.getBootCount()).thenReturn(1);
+ ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+ doNothing()
+ .when(mInjected)
+ .reportMetric(
+ metricsSuccessCaptor.capture(),
+ eq(0) /* error code */,
+ eq(2) /* Server based */,
+ eq(1) /* attempt count */,
+ anyInt(),
+ eq(0) /* vbmeta status */,
+ anyInt());
+ when(mServiceConnection.unwrap(any(), anyLong()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ mService.loadRebootEscrowDataIfAvailable(null);
+
+ verify(mServiceConnection).unwrap(any(), anyLong());
+ verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID));
+ verify(mCallbacks, never())
+ .onRebootEscrowRestored(anyByte(), any(), eq(SECURE_SECONDARY_USER_ID));
+ verify(mCallbacks, never())
+ .onRebootEscrowRestored(anyByte(), any(), eq(WORK_PROFILE_USER_ID));
+ verify(mCallbacks, never())
+ .onRebootEscrowRestored(anyByte(), any(), eq(NONSECURE_SECONDARY_USER_ID));
+ assertTrue(metricsSuccessCaptor.getValue());
+ }
+
+ @Test
+ public void loadRebootEscrowDataIfAvailable_noDataWorkProfile_Success() throws Exception {
+ setServerBasedRebootEscrowProvider();
+ RebootEscrowListener mockListener = mock(RebootEscrowListener.class);
+ mService.setRebootEscrowListener(mockListener);
+ mService.prepareRebootEscrow();
+
+ clearInvocations(mServiceConnection);
+
+ // escrow primary user and secondary user, don't escrow work profile
+ callToRebootEscrowIfNeededAndWait(PRIMARY_USER_ID);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+ callToRebootEscrowIfNeededAndWait(SECURE_SECONDARY_USER_ID);
+ verify(mockListener).onPreparedForReboot(eq(true));
+ verify(mServiceConnection, never()).wrapBlob(any(), anyLong(), anyLong());
+
+ when(mServiceConnection.wrapBlob(any(), anyLong(), anyLong()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+ assertEquals(ARM_REBOOT_ERROR_NONE, mService.armRebootEscrowIfNeeded());
+ verify(mServiceConnection).wrapBlob(any(), anyLong(), anyLong());
+
+ assertTrue(mStorage.hasRebootEscrow(PRIMARY_USER_ID));
+ assertTrue(mStorage.hasRebootEscrow(SECURE_SECONDARY_USER_ID));
+ assertFalse(mStorage.hasRebootEscrow(WORK_PROFILE_USER_ID));
+ assertTrue(mStorage.hasRebootEscrowServerBlob());
+
+ // pretend reboot happens here
+ when(mInjected.getBootCount()).thenReturn(1);
+ ArgumentCaptor<Boolean> metricsSuccessCaptor = ArgumentCaptor.forClass(Boolean.class);
+ doNothing()
+ .when(mInjected)
+ .reportMetric(
+ metricsSuccessCaptor.capture(),
+ eq(0) /* error code */,
+ eq(2) /* Server based */,
+ eq(1) /* attempt count */,
+ anyInt(),
+ eq(0) /* vbmeta status */,
+ anyInt());
+ when(mServiceConnection.unwrap(any(), anyLong()))
+ .thenAnswer(invocation -> invocation.getArgument(0));
+
+ mService.loadRebootEscrowDataIfAvailable(null);
+
+ verify(mServiceConnection).unwrap(any(), anyLong());
+ verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(PRIMARY_USER_ID));
+ verify(mCallbacks).onRebootEscrowRestored(anyByte(), any(), eq(SECURE_SECONDARY_USER_ID));
+ verify(mCallbacks, never())
+ .onRebootEscrowRestored(anyByte(), any(), eq(WORK_PROFILE_USER_ID));
+ verify(mCallbacks, never())
+ .onRebootEscrowRestored(anyByte(), any(), eq(NONSECURE_SECONDARY_USER_ID));
+ assertTrue(metricsSuccessCaptor.getValue());
+ }
+
+ @Test
public void loadRebootEscrowDataIfAvailable_ServerBased_Success() throws Exception {
setServerBasedRebootEscrowProvider();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index c7c97e4..8a7d276 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -57,12 +57,12 @@
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.FlagsParameterization;
import android.platform.test.flag.junit.SetFlagsRule;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
import com.android.internal.R;
import com.android.server.UiServiceTestCase;
@@ -79,9 +79,12 @@
import java.util.ArrayList;
import java.util.List;
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
@SmallTest
@SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the class.
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
public class GroupHelperTest extends UiServiceTestCase {
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
@@ -95,6 +98,16 @@
private GroupHelper mGroupHelper;
private @Mock Icon mSmallIcon;
+ @Parameters(name = "{0}")
+ public static List<FlagsParameterization> getParams() {
+ return FlagsParameterization.allCombinationsOf(
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST);
+ }
+
+ public GroupHelperTest(FlagsParameterization flags) {
+ mSetFlagsRule.setFlagsParameterization(flags);
+ }
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -708,7 +721,8 @@
}
@Test
- public void testDropToZeroRemoveGroup() {
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testDropToZeroRemoveGroup_disableFlag() {
final String pkg = "package";
List<StatusBarNotification> posted = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -736,7 +750,37 @@
}
@Test
- public void testAppStartsGrouping() {
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testDropToZeroRemoveGroup() {
+ final String pkg = "package";
+ List<StatusBarNotification> posted = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ posted.add(sbn);
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ Mockito.reset(mCallback);
+
+ for (int i = 0; i < AUTOGROUP_AT_COUNT - 1; i++) {
+ mGroupHelper.onNotificationRemoved(posted.remove(0));
+ }
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ Mockito.reset(mCallback);
+
+ mGroupHelper.onNotificationRemoved(posted.remove(0));
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString());
+ }
+
+ @Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAppStartsGrouping_disableFlag() {
final String pkg = "package";
List<StatusBarNotification> posted = new ArrayList<>();
for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
@@ -765,6 +809,36 @@
}
@Test
+ @EnableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
+ public void testAppStartsGrouping() {
+ final String pkg = "package";
+ List<StatusBarNotification> posted = new ArrayList<>();
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ final StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM);
+ posted.add(sbn);
+ mGroupHelper.onNotificationPosted(sbn, false);
+ }
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(getNotificationAttributes(BASE_FLAGS)));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ Mockito.reset(mCallback);
+
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ final StatusBarNotification sbn =
+ getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, "app group");
+ sbn.setOverrideGroupKey("autogrouped");
+ mGroupHelper.onNotificationPosted(sbn, true);
+ verify(mCallback, times(1)).removeAutoGroup(sbn.getKey());
+ if (i < AUTOGROUP_AT_COUNT - 1) {
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+ }
+ }
+ verify(mCallback, times(1)).removeAutoGroupSummary(anyInt(), anyString());
+ }
+
+ @Test
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
public void testNewNotificationsAddedToAutogroup_ifOriginalNotificationsCanceled_alwaysGroup() {
final String pkg = "package";
@@ -915,8 +989,9 @@
}
@Test
+ @DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
@EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
- public void testAddSummary_diffIcon_diffColor() {
+ public void testAddSummary_diffIcon_diffColor_disableFlag() {
final String pkg = "package";
final Icon initialIcon = mock(Icon.class);
when(initialIcon.sameAs(initialIcon)).thenReturn(true);
@@ -959,6 +1034,51 @@
}
@Test
+ @EnableFlags({Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE,
+ android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST})
+ public void testAddSummary_diffIcon_diffColor() {
+ final String pkg = "package";
+ final Icon initialIcon = mock(Icon.class);
+ when(initialIcon.sameAs(initialIcon)).thenReturn(true);
+ final int initialIconColor = Color.BLUE;
+
+ // Spy GroupHelper for getMonochromeAppIcon
+ final Icon monochromeIcon = mock(Icon.class);
+ when(monochromeIcon.sameAs(monochromeIcon)).thenReturn(true);
+ GroupHelper groupHelper = spy(mGroupHelper);
+ doReturn(monochromeIcon).when(groupHelper).getMonochromeAppIcon(eq(pkg));
+
+ final NotificationAttributes initialAttr = new NotificationAttributes(BASE_FLAGS,
+ initialIcon, initialIconColor, DEFAULT_VISIBILITY);
+
+ // Add notifications with same icon and color
+ for (int i = 0; i < AUTOGROUP_AT_COUNT; i++) {
+ StatusBarNotification sbn = getSbn(pkg, i, String.valueOf(i), UserHandle.SYSTEM, null,
+ initialIcon, initialIconColor);
+ groupHelper.onNotificationPosted(sbn, false);
+ }
+ // Check that the summary would have the same icon and color
+ verify(mCallback, times(1)).addAutoGroupSummary(
+ anyInt(), eq(pkg), anyString(), eq(initialAttr));
+ verify(mCallback, times(AUTOGROUP_AT_COUNT - 1)).addAutoGroup(anyString(), anyBoolean());
+ verify(mCallback, never()).removeAutoGroup(anyString());
+ verify(mCallback, never()).removeAutoGroupSummary(anyInt(), anyString());
+
+ // After auto-grouping, add new notification with a different color
+ final Icon newIcon = mock(Icon.class);
+ final int newIconColor = Color.YELLOW;
+ StatusBarNotification sbn = getSbn(pkg, AUTOGROUP_AT_COUNT,
+ String.valueOf(AUTOGROUP_AT_COUNT), UserHandle.SYSTEM, null, newIcon,
+ newIconColor);
+ groupHelper.onNotificationPosted(sbn, true);
+
+ // Summary should be updated to the default color and the icon to the monochrome icon
+ NotificationAttributes newAttr = new NotificationAttributes(BASE_FLAGS, monochromeIcon,
+ COLOR_DEFAULT, DEFAULT_VISIBILITY);
+ verify(mCallback, times(1)).updateAutogroupSummary(anyInt(), anyString(), eq(newAttr));
+ }
+
+ @Test
@DisableFlags(android.app.Flags.FLAG_CHECK_AUTOGROUP_BEFORE_POST)
@EnableFlags(Flags.FLAG_AUTOGROUP_SUMMARY_ICON_UPDATE)
public void testAddSummary_diffVisibility_alwaysAutogroup() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 4a9760b..e91fd37 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2726,6 +2726,19 @@
assertNoStartingWindow(activity);
}
+ @Test
+ public void testPostCleanupStartingWindow() {
+ registerTestStartingWindowOrganizer();
+ final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
+ activity.addStartingWindow(mPackageName, android.R.style.Theme, null, true, true, false,
+ true, false, false, false);
+ waitUntilHandlersIdle();
+ assertHasStartingWindow(activity);
+ // Simulate Shell remove starting window actively.
+ activity.mStartingWindow.removeImmediately();
+ assertNoStartingWindow(activity);
+ }
+
private void testLegacySplashScreen(int targetSdk, int verifyType) {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity.mTargetSdk = targetSdk;
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index d5db612..65e8e13 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -555,10 +555,11 @@
* Set the device's carrier restriction status
*
* @param carrierRestrictionStatus device restriction status
- * @hide
*/
public @NonNull
- Builder setCarrierRestrictionStatus(int carrierRestrictionStatus) {
+ @FlaggedApi(Flags.FLAG_SET_CARRIER_RESTRICTION_STATUS)
+ Builder setCarrierRestrictionStatus(
+ @CarrierRestrictionStatus int carrierRestrictionStatus) {
mRules.mCarrierRestrictionStatus = carrierRestrictionStatus;
return this;
}
diff --git a/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.aidl b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.aidl
new file mode 100644
index 0000000..ecd248c
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.telephony.satellite;
+
+ parcelable SystemSelectionSpecifier;
diff --git a/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java
new file mode 100644
index 0000000..8a5e7f2
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SystemSelectionSpecifier.java
@@ -0,0 +1,175 @@
+/*
+ * 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.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.IntArray;
+
+import java.util.Objects;
+
+/**
+ * @hide
+ */
+public final class SystemSelectionSpecifier implements Parcelable {
+
+ /** Network plmn associated with channel information. */
+ @NonNull private String mMccMnc;
+
+ /** The frequency bands to scan. Maximum length of the vector is 8. */
+ @NonNull private IntArray mBands;
+
+ /**
+ * The radio channels to scan as defined in 3GPP TS 25.101 and 36.101.
+ * Maximum length of the vector is 32.
+ */
+ @NonNull private IntArray mEarfcs;
+
+ /**
+ * @hide
+ */
+ public SystemSelectionSpecifier(@NonNull String mccmnc, @NonNull IntArray bands,
+ @NonNull IntArray earfcs) {
+ mMccMnc = mccmnc;
+ mBands = bands;
+ mEarfcs = earfcs;
+ }
+
+ private SystemSelectionSpecifier(Parcel in) {
+ readFromParcel(in);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(@NonNull Parcel out, int flags) {
+ mMccMnc = TextUtils.emptyIfNull(mMccMnc);
+ out.writeString8(mMccMnc);
+
+ if (mBands != null && mBands.size() > 0) {
+ out.writeInt(mBands.size());
+ for (int i = 0; i < mBands.size(); i++) {
+ out.writeInt(mBands.get(i));
+ }
+ } else {
+ out.writeInt(0);
+ }
+
+ if (mEarfcs != null && mEarfcs.size() > 0) {
+ out.writeInt(mEarfcs.size());
+ for (int i = 0; i < mEarfcs.size(); i++) {
+ out.writeInt(mEarfcs.get(i));
+ }
+ } else {
+ out.writeInt(0);
+ }
+ }
+
+ @NonNull public static final Parcelable.Creator<SystemSelectionSpecifier> CREATOR =
+ new Creator<>() {
+ @Override
+ public SystemSelectionSpecifier createFromParcel(Parcel in) {
+ return new SystemSelectionSpecifier(in);
+ }
+
+ @Override
+ public SystemSelectionSpecifier[] newArray(int size) {
+ return new SystemSelectionSpecifier[size];
+ }
+ };
+
+ @Override
+ @NonNull public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("mccmnc:");
+ sb.append(mMccMnc);
+ sb.append(",");
+
+ sb.append("bands:");
+ if (mBands != null && mBands.size() > 0) {
+ for (int i = 0; i < mBands.size(); i++) {
+ sb.append(mBands.get(i));
+ sb.append(",");
+ }
+ } else {
+ sb.append("none,");
+ }
+
+ sb.append("earfcs:");
+ if (mEarfcs != null && mEarfcs.size() > 0) {
+ for (int i = 0; i < mEarfcs.size(); i++) {
+ sb.append(mEarfcs.get(i));
+ sb.append(",");
+ }
+ } else {
+ sb.append("none");
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ SystemSelectionSpecifier that = (SystemSelectionSpecifier) o;
+ return Objects.equals(mMccMnc, that.mMccMnc)
+ && Objects.equals(mBands, that.mBands)
+ && Objects.equals(mEarfcs, that.mEarfcs);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(mMccMnc, mBands, mEarfcs);
+ }
+
+ @NonNull public String getMccMnc() {
+ return mMccMnc;
+ }
+
+ @NonNull public IntArray getBands() {
+ return mBands;
+ }
+
+ @NonNull public IntArray getEarfcs() {
+ return mEarfcs;
+ }
+
+ private void readFromParcel(Parcel in) {
+ mMccMnc = in.readString();
+
+ mBands = new IntArray();
+ int numBands = in.readInt();
+ if (numBands > 0) {
+ for (int i = 0; i < numBands; i++) {
+ mBands.add(in.readInt());
+ }
+ }
+
+ mEarfcs = new IntArray();
+ int numEarfcs = in.readInt();
+ if (numEarfcs > 0) {
+ for (int i = 0; i < numEarfcs; i++) {
+ mEarfcs.add(in.readInt());
+ }
+ }
+ }
+}
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 16983a0..b82396e 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -23,6 +23,7 @@
import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
import android.telephony.satellite.stub.ISatelliteListener;
import android.telephony.satellite.stub.SatelliteDatagram;
+import android.telephony.satellite.stub.SystemSelectionSpecifier;
/**
* {@hide}
@@ -500,4 +501,21 @@
* SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
*/
void abortSendingSatelliteDatagrams(in IIntegerConsumer resultCallback);
+
+ /**
+ * Request to update the satellite subscription to be used for Non-Terrestrial network.
+ *
+ * @param iccId The ICCID of the subscription
+ * @param resultCallback The callback to receive the error code result of the operation.
+ */
+ void updateSatelliteSubscription(in String iccId, in IIntegerConsumer resultCallback);
+
+ /**
+ * Request to update system selection channels
+ *
+ * @param systemSelectionSpecifiers list of system selection specifiers
+ * @param resultCallback The callback to receive the error code result of the operation.
+ */
+ void updateSystemSelectionChannels(in List<SystemSelectionSpecifier> systemSelectionSpecifiers,
+ in IIntegerConsumer resultCallback);
}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index a623633..d8b4974 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -262,6 +262,22 @@
"abortSendingSatelliteDatagrams");
}
+ @Override
+ public void updateSatelliteSubscription(String iccId, IIntegerConsumer resultCallback)
+ throws RemoteException {
+ executeMethodAsync(() -> SatelliteImplBase.this.updateSatelliteSubscription(
+ iccId, resultCallback), "updateSatelliteSubscription");
+ }
+
+ @Override
+ public void updateSystemSelectionChannels(
+ List<SystemSelectionSpecifier> systemSelectionSpecifiers,
+ IIntegerConsumer resultCallback) throws RemoteException {
+ executeMethodAsync(() -> SatelliteImplBase.this.updateSystemSelectionChannels(
+ systemSelectionSpecifiers, resultCallback),
+ "updateSystemSelectionChannels");
+ }
+
// Call the methods with a clean calling identity on the executor and wait indefinitely for
// the future to return.
private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -768,4 +784,27 @@
public void abortSendingSatelliteDatagrams(@NonNull IIntegerConsumer resultCallback){
// stub implementation
}
+
+ /**
+ * Request to update the satellite subscription to be used for Non-Terrestrial network.
+ *
+ * @param iccId The ICCID of the subscription
+ * @param resultCallback The callback to receive the error code result of the operation.
+ */
+ public void updateSatelliteSubscription(String iccId,
+ @NonNull IIntegerConsumer resultCallback) {
+ // stub implementation
+ }
+
+ /**
+ * Request to update system selection channels
+ *
+ * @param systemSelectionSpecifiers list of system selection specifiers
+ * @param resultCallback The callback to receive the error code result of the operation.
+ */
+ public void updateSystemSelectionChannels(
+ @NonNull List<SystemSelectionSpecifier> systemSelectionSpecifiers,
+ @NonNull IIntegerConsumer resultCallback) {
+ // stub implementation
+ }
}
diff --git a/telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl b/telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl
new file mode 100644
index 0000000..22240f6
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/SystemSelectionSpecifier.aidl
@@ -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 android.telephony.satellite.stub;
+
+/**
+ * {@hide}
+ */
+parcelable SystemSelectionSpecifier {
+ /** Network plmn associated with channel information. */
+ String mMccMnc;
+
+ /**
+ * The frequency bands to scan. Bands and earfcns won't overlap.
+ * Bands will be filled only if the whole band is needed.
+ * Maximum length of the vector is 8.
+ */
+ int[] mBands;
+
+ /**
+ * The radio channels to scan as defined in 3GPP TS 25.101 and 36.101.
+ * Maximum length of the vector is 32.
+ */
+ int[] mEarfcs;
+}
diff --git a/tests/FlickerTests/ActivityEmbedding/Android.bp b/tests/FlickerTests/ActivityEmbedding/Android.bp
index 2cdf542..e09fbf6 100644
--- a/tests/FlickerTests/ActivityEmbedding/Android.bp
+++ b/tests/FlickerTests/ActivityEmbedding/Android.bp
@@ -20,17 +20,65 @@
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
+ default_team: "trendy_team_windowing_sdk",
default_applicable_licenses: ["frameworks_base_license"],
}
-android_test {
- name: "FlickerTestsOther",
+filegroup {
+ name: "FlickerTestsOtherCommon-src",
+ srcs: ["src/**/ActivityEmbeddingTestBase.kt"],
+}
+
+filegroup {
+ name: "FlickerTestsOtherOpen-src",
+ srcs: ["src/**/open/*"],
+}
+
+filegroup {
+ name: "FlickerTestsOtherRotation-src",
+ srcs: ["src/**/rotation/*"],
+}
+
+java_library {
+ name: "FlickerTestsOtherCommon",
+ defaults: ["FlickerTestsDefault"],
+ srcs: [":FlickerTestsOtherCommon-src"],
+ static_libs: ["FlickerTestsBase"],
+}
+
+java_defaults {
+ name: "FlickerTestsOtherDefaults",
defaults: ["FlickerTestsDefault"],
manifest: "AndroidManifest.xml",
package_name: "com.android.server.wm.flicker",
instrumentation_target_package: "com.android.server.wm.flicker",
test_config_template: "AndroidTestTemplate.xml",
- srcs: ["src/**/*"],
- static_libs: ["FlickerTestsBase"],
+ static_libs: [
+ "FlickerTestsBase",
+ "FlickerTestsOtherCommon",
+ ],
data: ["trace_config/*"],
}
+
+android_test {
+ name: "FlickerTestsOtherOpen",
+ defaults: ["FlickerTestsOtherDefaults"],
+ srcs: [":FlickerTestsOtherOpen-src"],
+}
+
+android_test {
+ name: "FlickerTestsOtherRotation",
+ defaults: ["FlickerTestsOtherDefaults"],
+ srcs: [":FlickerTestsOtherRotation-src"],
+}
+
+android_test {
+ name: "FlickerTestsOther",
+ defaults: ["FlickerTestsOtherDefaults"],
+ srcs: ["src/**/*"],
+ exclude_srcs: [
+ ":FlickerTestsOtherOpen-src",
+ ":FlickerTestsOtherRotation-src",
+ ":FlickerTestsOtherCommon-src",
+ ],
+}
diff --git a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt
index 8a241de..209a14b 100644
--- a/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt
+++ b/tests/FlickerTests/FlickerService/src/com/android/server/wm/flicker/service/Utils.kt
@@ -17,6 +17,7 @@
package com.android.server.wm.flicker.service
import android.app.Instrumentation
+import android.platform.test.rule.DisableNotificationCooldownSettingRule
import android.platform.test.rule.NavigationModeRule
import android.platform.test.rule.PressHomeRule
import android.platform.test.rule.UnlockScreenRule
@@ -48,6 +49,7 @@
clearCacheAfterParsing = false
)
)
+ .around(DisableNotificationCooldownSettingRule())
.around(PressHomeRule())
}
}
diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp
index 3538949c..ccc3683 100644
--- a/tests/FlickerTests/IME/Android.bp
+++ b/tests/FlickerTests/IME/Android.bp
@@ -39,6 +39,10 @@
defaults: ["FlickerTestsDefault"],
manifest: "AndroidManifest.xml",
test_config_template: "AndroidTestTemplate.xml",
+ test_suites: [
+ "device-tests",
+ "device-platinum-tests",
+ ],
srcs: ["src/**/*"],
static_libs: ["FlickerTestsBase"],
data: ["trace_config/*"],
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
index dc50135..ed6e8df 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/CloseImeShownOnAppStartToAppOnPressBackTest.kt
@@ -23,6 +23,7 @@
import android.tools.flicker.legacy.LegacyFlickerTest
import android.tools.flicker.legacy.LegacyFlickerTestFactory
import android.tools.traces.component.ComponentNameMatcher
+import androidx.test.filters.FlakyTest
import com.android.server.wm.flicker.BaseTest
import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
import org.junit.FixMethodOrder
@@ -77,6 +78,7 @@
@Presubmit @Test fun imeLayerBecomesInvisible() = flicker.imeLayerBecomesInvisible()
+ @FlakyTest(bugId = 330486656)
@Presubmit
@Test
fun imeAppLayerIsAlwaysVisible() {
diff --git a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
index c29e71c..07fc230 100644
--- a/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
+++ b/tests/FlickerTests/Notification/src/com/android/server/wm/flicker/notification/OpenAppFromNotificationWarmTest.kt
@@ -18,6 +18,7 @@
import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
+import android.platform.test.rule.DisableNotificationCooldownSettingRule
import android.tools.flicker.junit.FlickerParametersRunnerFactory
import android.tools.flicker.legacy.FlickerBuilder
import android.tools.flicker.legacy.FlickerTestData
@@ -37,6 +38,7 @@
import com.android.server.wm.flicker.taskBarLayerIsVisibleAtEnd
import com.android.server.wm.flicker.taskBarWindowIsVisibleAtEnd
import org.junit.Assume
+import org.junit.ClassRule
import org.junit.FixMethodOrder
import org.junit.Ignore
import org.junit.Test
@@ -208,5 +210,10 @@
@Parameterized.Parameters(name = "{0}")
@JvmStatic
fun getParams() = LegacyFlickerTestFactory.nonRotationTests()
+
+ /** Ensures that posted notifications will alert and HUN even just after boot. */
+ @ClassRule
+ @JvmField
+ val disablenotificationCooldown = DisableNotificationCooldownSettingRule()
}
}
diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp
index a85d809..c0cbdc3 100644
--- a/tests/Input/Android.bp
+++ b/tests/Input/Android.bp
@@ -31,6 +31,7 @@
"androidx.test.runner",
"androidx.test.uiautomator_uiautomator",
"compatibility-device-util-axt",
+ "cts-input-lib",
"flag-junit",
"frameworks-base-testutils",
"hamcrest-library",
diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt
index 4893d14..6b95f5c 100644
--- a/tests/Input/src/com/android/test/input/AnrTest.kt
+++ b/tests/Input/src/com/android/test/input/AnrTest.kt
@@ -21,21 +21,23 @@
import android.app.ActivityManager
import android.app.ApplicationExitInfo
+import android.content.Context
import android.graphics.Rect
+import android.hardware.display.DisplayManager
import android.os.Build
import android.os.IInputConstants.UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS
import android.os.SystemClock
import android.provider.Settings
import android.provider.Settings.Global.HIDE_ERROR_DIALOGS
import android.testing.PollingCheck
-import android.view.InputDevice
-import android.view.MotionEvent
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.UiObject2
import androidx.test.uiautomator.Until
+import com.android.cts.input.UinputTouchScreen
+
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
@@ -150,6 +152,18 @@
assertEquals(ApplicationExitInfo.REASON_ANR, reasons[0].reason)
}
+ private fun clickOnObject(obj: UiObject2) {
+ val displayManager =
+ instrumentation.context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager
+ val display = displayManager.getDisplay(obj.getDisplayId())
+ val touchScreen = UinputTouchScreen(instrumentation, display)
+
+ val rect: Rect = obj.visibleBounds
+ val pointer = touchScreen.touchDown(rect.centerX(), rect.centerY())
+ pointer.lift()
+ touchScreen.close()
+ }
+
private fun triggerAnr() {
startUnresponsiveActivity()
val uiDevice: UiDevice = UiDevice.getInstance(instrumentation)
@@ -160,13 +174,7 @@
return
}
- val rect: Rect = obj.visibleBounds
- val downTime = SystemClock.uptimeMillis()
- val downEvent = MotionEvent.obtain(downTime, downTime,
- MotionEvent.ACTION_DOWN, rect.left.toFloat(), rect.top.toFloat(), 0 /* metaState */)
- downEvent.source = InputDevice.SOURCE_TOUCHSCREEN
-
- instrumentation.uiAutomation.injectInputEvent(downEvent, false /* sync*/)
+ clickOnObject(obj)
SystemClock.sleep(DISPATCHING_TIMEOUT.toLong()) // default ANR timeout for gesture monitors
}
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index a8b383c..093923f 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -101,8 +101,8 @@
private static final String OBSERVER_NAME_2 = "observer2";
private static final String OBSERVER_NAME_3 = "observer3";
private static final String OBSERVER_NAME_4 = "observer4";
- private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(10);
- private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(50);
+ private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1);
+ private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5);
@Rule
public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -1453,8 +1453,7 @@
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
- moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS
- - TimeUnit.MINUTES.toMillis(1));
+ moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_DEESCALATION_WINDOW_MS);
// The first failure will be outside the threshold.
raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
@@ -1713,9 +1712,6 @@
watchdog.onPackageFailure(packages, failureReason);
}
mTestLooper.dispatchAll();
- if (Flags.recoverabilityDetection()) {
- moveTimeForwardAndDispatch(watchdog.DEFAULT_MITIGATION_WINDOW_MS);
- }
}
private PackageWatchdog createWatchdog() {
diff --git a/tests/TrustTests/AndroidManifest.xml b/tests/TrustTests/AndroidManifest.xml
index 30cf345..2f6c0dd 100644
--- a/tests/TrustTests/AndroidManifest.xml
+++ b/tests/TrustTests/AndroidManifest.xml
@@ -78,6 +78,7 @@
<action android:name="android.service.trust.TrustAgentService" />
</intent-filter>
</service>
+
<service
android:name=".IsActiveUnlockRunningTrustAgent"
android:exported="true"
@@ -88,6 +89,16 @@
</intent-filter>
</service>
+ <service
+ android:name=".UnlockAttemptTrustAgent"
+ android:exported="true"
+ android:label="Test Agent"
+ android:permission="android.permission.BIND_TRUST_AGENT">
+ <intent-filter>
+ <action android:name="android.service.trust.TrustAgentService" />
+ </intent-filter>
+ </service>
+
</application>
<!-- self-instrumenting test package. -->
diff --git a/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
new file mode 100644
index 0000000..2c9361d
--- /dev/null
+++ b/tests/TrustTests/src/android/trust/test/UnlockAttemptTest.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.trust.test
+
+import android.app.trust.TrustManager
+import android.content.Context
+import android.trust.BaseTrustAgentService
+import android.trust.TrustTestActivity
+import android.trust.test.lib.LockStateTrackingRule
+import android.trust.test.lib.ScreenLockRule
+import android.trust.test.lib.TestTrustListener
+import android.trust.test.lib.TrustAgentRule
+import android.util.Log
+import androidx.test.core.app.ApplicationProvider.getApplicationContext
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.RuleChain
+import org.junit.runner.RunWith
+
+/**
+ * Test for the impacts of reporting unlock attempts.
+ *
+ * atest TrustTests:UnlockAttemptTest
+ */
+@RunWith(AndroidJUnit4::class)
+class UnlockAttemptTest {
+ private val context = getApplicationContext<Context>()
+ private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
+ private val userId = context.userId
+ private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
+ private val screenLockRule = ScreenLockRule(requireStrongAuth = true)
+ private val lockStateTrackingRule = LockStateTrackingRule()
+ private val trustAgentRule =
+ TrustAgentRule<UnlockAttemptTrustAgent>(startUnlocked = false, startEnabled = false)
+
+ private val trustListener = UnlockAttemptTrustListener()
+ private val agent get() = trustAgentRule.agent
+
+ @get:Rule
+ val rule: RuleChain =
+ RuleChain.outerRule(activityScenarioRule)
+ .around(screenLockRule)
+ .around(lockStateTrackingRule)
+ .around(trustAgentRule)
+
+ @Before
+ fun setUp() {
+ trustManager.registerTrustListener(trustListener)
+ }
+
+ @Test
+ fun successfulUnlockAttempt_allowsTrustAgentToStart() =
+ runUnlockAttemptTest(enableAndVerifyTrustAgent = false, managingTrust = false) {
+ trustAgentRule.enableTrustAgent()
+
+ triggerSuccessfulUnlock()
+
+ trustAgentRule.verifyAgentIsRunning(MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START)
+ }
+
+ @Test
+ fun successfulUnlockAttempt_notifiesTrustAgent() =
+ runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
+ val oldSuccessfulCount = agent.successfulUnlockCallCount
+ val oldFailedCount = agent.failedUnlockCallCount
+
+ triggerSuccessfulUnlock()
+
+ assertThat(agent.successfulUnlockCallCount).isEqualTo(oldSuccessfulCount + 1)
+ assertThat(agent.failedUnlockCallCount).isEqualTo(oldFailedCount)
+ }
+
+ @Test
+ fun successfulUnlockAttempt_notifiesTrustListenerOfManagedTrust() =
+ runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
+ val oldTrustManagedChangedCount = trustListener.onTrustManagedChangedCount[userId] ?: 0
+
+ triggerSuccessfulUnlock()
+
+ assertThat(trustListener.onTrustManagedChangedCount[userId] ?: 0).isEqualTo(
+ oldTrustManagedChangedCount + 1
+ )
+ }
+
+ @Test
+ fun failedUnlockAttempt_doesNotAllowTrustAgentToStart() =
+ runUnlockAttemptTest(enableAndVerifyTrustAgent = false, managingTrust = false) {
+ trustAgentRule.enableTrustAgent()
+
+ triggerFailedUnlock()
+
+ trustAgentRule.ensureAgentIsNotRunning(MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START)
+ }
+
+ @Test
+ fun failedUnlockAttempt_notifiesTrustAgent() =
+ runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
+ val oldSuccessfulCount = agent.successfulUnlockCallCount
+ val oldFailedCount = agent.failedUnlockCallCount
+
+ triggerFailedUnlock()
+
+ assertThat(agent.successfulUnlockCallCount).isEqualTo(oldSuccessfulCount)
+ assertThat(agent.failedUnlockCallCount).isEqualTo(oldFailedCount + 1)
+ }
+
+ @Test
+ fun failedUnlockAttempt_doesNotNotifyTrustListenerOfManagedTrust() =
+ runUnlockAttemptTest(enableAndVerifyTrustAgent = true, managingTrust = true) {
+ val oldTrustManagedChangedCount = trustListener.onTrustManagedChangedCount[userId] ?: 0
+
+ triggerFailedUnlock()
+
+ assertThat(trustListener.onTrustManagedChangedCount[userId] ?: 0).isEqualTo(
+ oldTrustManagedChangedCount
+ )
+ }
+
+ private fun runUnlockAttemptTest(
+ enableAndVerifyTrustAgent: Boolean,
+ managingTrust: Boolean,
+ testBlock: () -> Unit,
+ ) {
+ if (enableAndVerifyTrustAgent) {
+ Log.i(TAG, "Triggering successful unlock")
+ triggerSuccessfulUnlock()
+ Log.i(TAG, "Enabling and waiting for trust agent")
+ trustAgentRule.enableAndVerifyTrustAgentIsRunning(
+ MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START
+ )
+ Log.i(TAG, "Managing trust: $managingTrust")
+ agent.setManagingTrust(managingTrust)
+ await()
+ }
+ testBlock()
+ }
+
+ private fun triggerSuccessfulUnlock() {
+ screenLockRule.successfulScreenLockAttempt()
+ trustAgentRule.reportSuccessfulUnlock()
+ await()
+ }
+
+ private fun triggerFailedUnlock() {
+ screenLockRule.failedScreenLockAttempt()
+ trustAgentRule.reportFailedUnlock()
+ await()
+ }
+
+ companion object {
+ private const val TAG = "UnlockAttemptTest"
+ private fun await(millis: Long = 500) = Thread.sleep(millis)
+ private const val MAX_WAIT_FOR_ENABLED_TRUST_AGENT_TO_START = 10000L
+ }
+}
+
+class UnlockAttemptTrustAgent : BaseTrustAgentService() {
+ var successfulUnlockCallCount: Long = 0
+ private set
+ var failedUnlockCallCount: Long = 0
+ private set
+
+ override fun onUnlockAttempt(successful: Boolean) {
+ super.onUnlockAttempt(successful)
+ if (successful) {
+ successfulUnlockCallCount++
+ } else {
+ failedUnlockCallCount++
+ }
+ }
+}
+
+private class UnlockAttemptTrustListener : TestTrustListener() {
+ var enabledTrustAgentsChangedCount = mutableMapOf<Int, Int>()
+ var onTrustManagedChangedCount = mutableMapOf<Int, Int>()
+
+ override fun onEnabledTrustAgentsChanged(userId: Int) {
+ enabledTrustAgentsChangedCount.compute(userId) { _: Int, curr: Int? ->
+ if (curr == null) 0 else curr + 1
+ }
+ }
+
+ data class TrustChangedParams(
+ val enabled: Boolean,
+ val newlyUnlocked: Boolean,
+ val userId: Int,
+ val flags: Int,
+ val trustGrantedMessages: MutableList<String>?
+ )
+
+ val onTrustChangedCalls = mutableListOf<TrustChangedParams>()
+
+ override fun onTrustChanged(
+ enabled: Boolean,
+ newlyUnlocked: Boolean,
+ userId: Int,
+ flags: Int,
+ trustGrantedMessages: MutableList<String>
+ ) {
+ onTrustChangedCalls += TrustChangedParams(
+ enabled, newlyUnlocked, userId, flags, trustGrantedMessages
+ )
+ }
+
+ override fun onTrustManagedChanged(enabled: Boolean, userId: Int) {
+ onTrustManagedChangedCount.compute(userId) { _: Int, curr: Int? ->
+ if (curr == null) 0 else curr + 1
+ }
+ }
+}
diff --git a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
index f1edca3..1ccdcc6 100644
--- a/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/ScreenLockRule.kt
@@ -24,6 +24,8 @@
import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
import androidx.test.uiautomator.UiDevice
import com.android.internal.widget.LockPatternUtils
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
+import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
import com.android.internal.widget.LockscreenCredential
import com.google.common.truth.Truth.assertWithMessage
import org.junit.rules.TestRule
@@ -32,13 +34,18 @@
/**
* Sets a screen lock on the device for the duration of the test.
+ *
+ * @param requireStrongAuth Whether a strong auth is required at the beginning.
+ * If true, trust agents will not be available until the user verifies their credentials.
*/
-class ScreenLockRule : TestRule {
+class ScreenLockRule(val requireStrongAuth: Boolean = false) : TestRule {
private val context: Context = getApplicationContext()
+ private val userId = context.userId
private val uiDevice = UiDevice.getInstance(getInstrumentation())
private val windowManager = checkNotNull(WindowManagerGlobal.getWindowManagerService())
private val lockPatternUtils = LockPatternUtils(context)
private var instantLockSavedValue = false
+ private var strongAuthSavedValue: Int = 0
override fun apply(base: Statement, description: Description) = object : Statement() {
override fun evaluate() {
@@ -46,10 +53,12 @@
dismissKeyguard()
setScreenLock()
setLockOnPowerButton()
+ configureStrongAuthState()
try {
base.evaluate()
} finally {
+ restoreStrongAuthState()
removeScreenLock()
revertLockOnPowerButton()
dismissKeyguard()
@@ -57,6 +66,22 @@
}
}
+ private fun configureStrongAuthState() {
+ strongAuthSavedValue = lockPatternUtils.getStrongAuthForUser(userId)
+ if (requireStrongAuth) {
+ Log.d(TAG, "Triggering strong auth due to simulated lockdown")
+ lockPatternUtils.requireStrongAuth(STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN, userId)
+ wait("strong auth required after lockdown") {
+ lockPatternUtils.getStrongAuthForUser(userId) ==
+ STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN
+ }
+ }
+ }
+
+ private fun restoreStrongAuthState() {
+ lockPatternUtils.requireStrongAuth(strongAuthSavedValue, userId)
+ }
+
private fun verifyNoScreenLockAlreadySet() {
assertWithMessage("Screen Lock must not already be set on device")
.that(lockPatternUtils.isSecure(context.userId))
@@ -82,6 +107,22 @@
}
}
+ fun successfulScreenLockAttempt() {
+ lockPatternUtils.verifyCredential(LockscreenCredential.createPin(PIN), context.userId, 0)
+ lockPatternUtils.userPresent(context.userId)
+ wait("strong auth not required") {
+ lockPatternUtils.getStrongAuthForUser(context.userId) == STRONG_AUTH_NOT_REQUIRED
+ }
+ }
+
+ fun failedScreenLockAttempt() {
+ lockPatternUtils.verifyCredential(
+ LockscreenCredential.createPin(WRONG_PIN),
+ context.userId,
+ 0
+ )
+ }
+
private fun setScreenLock() {
lockPatternUtils.setLockCredential(
LockscreenCredential.createPin(PIN),
@@ -121,5 +162,6 @@
companion object {
private const val TAG = "ScreenLockRule"
private const val PIN = "0000"
+ private const val WRONG_PIN = "0001"
}
}
diff --git a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
index 18bc029..404c6d9 100644
--- a/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/TrustAgentRule.kt
@@ -20,14 +20,15 @@
import android.content.ComponentName
import android.content.Context
import android.trust.BaseTrustAgentService
+import android.trust.test.lib.TrustAgentRule.Companion.invoke
import android.util.Log
import androidx.test.core.app.ApplicationProvider.getApplicationContext
import com.android.internal.widget.LockPatternUtils
import com.google.common.truth.Truth.assertWithMessage
+import kotlin.reflect.KClass
import org.junit.rules.TestRule
import org.junit.runner.Description
import org.junit.runners.model.Statement
-import kotlin.reflect.KClass
/**
* Enables a trust agent and causes the system service to bind to it.
@@ -37,7 +38,9 @@
* @constructor Creates the rule. Do not use; instead, use [invoke].
*/
class TrustAgentRule<T : BaseTrustAgentService>(
- private val serviceClass: KClass<T>
+ private val serviceClass: KClass<T>,
+ private val startUnlocked: Boolean,
+ private val startEnabled: Boolean,
) : TestRule {
private val context: Context = getApplicationContext()
private val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
@@ -48,11 +51,18 @@
override fun apply(base: Statement, description: Description) = object : Statement() {
override fun evaluate() {
verifyTrustServiceRunning()
- unlockDeviceWithCredential()
- enableTrustAgent()
+ if (startUnlocked) {
+ reportSuccessfulUnlock()
+ } else {
+ Log.i(TAG, "Trust manager not starting in unlocked state")
+ }
try {
- verifyAgentIsRunning()
+ if (startEnabled) {
+ enableAndVerifyTrustAgentIsRunning()
+ } else {
+ Log.i(TAG, "Trust agent ${serviceClass.simpleName} not enabled")
+ }
base.evaluate()
} finally {
disableTrustAgent()
@@ -64,12 +74,22 @@
assertWithMessage("Trust service is not running").that(trustManager).isNotNull()
}
- private fun unlockDeviceWithCredential() {
- Log.d(TAG, "Unlocking device with credential")
+ fun reportSuccessfulUnlock() {
+ Log.i(TAG, "Reporting successful unlock")
trustManager.reportUnlockAttempt(true, context.userId)
}
- private fun enableTrustAgent() {
+ fun reportFailedUnlock() {
+ Log.i(TAG, "Reporting failed unlock")
+ trustManager.reportUnlockAttempt(false, context.userId)
+ }
+
+ fun enableAndVerifyTrustAgentIsRunning(maxWait: Long = 30000L) {
+ enableTrustAgent()
+ verifyAgentIsRunning(maxWait)
+ }
+
+ fun enableTrustAgent() {
val componentName = ComponentName(context, serviceClass.java)
val userId = context.userId
Log.i(TAG, "Enabling trust agent ${componentName.flattenToString()} for user $userId")
@@ -79,12 +99,18 @@
lockPatternUtils.setEnabledTrustAgents(agents, userId)
}
- private fun verifyAgentIsRunning() {
- wait("${serviceClass.simpleName} to be running") {
+ fun verifyAgentIsRunning(maxWait: Long = 30000L) {
+ wait("${serviceClass.simpleName} to be running", maxWait) {
BaseTrustAgentService.instance(serviceClass) != null
}
}
+ fun ensureAgentIsNotRunning(window: Long = 30000L) {
+ ensure("${serviceClass.simpleName} is not running", window) {
+ BaseTrustAgentService.instance(serviceClass) == null
+ }
+ }
+
private fun disableTrustAgent() {
val componentName = ComponentName(context, serviceClass.java)
val userId = context.userId
@@ -97,13 +123,23 @@
companion object {
/**
- * Creates a new rule for the specified agent class. Example usage:
+ * Creates a new rule for the specified agent class. Starts with the device unlocked and
+ * the trust agent enabled. Example usage:
* ```
* @get:Rule val rule = TrustAgentRule<MyTestAgent>()
* ```
+ *
+ * Also supports setting different device lock and trust agent enablement states:
+ * ```
+ * @get:Rule val rule = TrustAgentRule<MyTestAgent>(startUnlocked = false, startEnabled = false)
+ * ```
*/
- inline operator fun <reified T : BaseTrustAgentService> invoke() =
- TrustAgentRule(T::class)
+ inline operator fun <reified T : BaseTrustAgentService> invoke(
+ startUnlocked: Boolean = true,
+ startEnabled: Boolean = true,
+ ) =
+ TrustAgentRule(T::class, startUnlocked, startEnabled)
+
private const val TAG = "TrustAgentRule"
}
diff --git a/tests/TrustTests/src/android/trust/test/lib/utils.kt b/tests/TrustTests/src/android/trust/test/lib/Utils.kt
similarity index 63%
rename from tests/TrustTests/src/android/trust/test/lib/utils.kt
rename to tests/TrustTests/src/android/trust/test/lib/Utils.kt
index e047202..3b32b47 100644
--- a/tests/TrustTests/src/android/trust/test/lib/utils.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/Utils.kt
@@ -39,7 +39,7 @@
) {
var waited = 0L
var count = 0
- while (!conditionFunction.invoke(count)) {
+ while (!conditionFunction(count)) {
assertWithMessage("Condition exceeded maximum wait time of $maxWait ms: $description")
.that(waited <= maxWait)
.isTrue()
@@ -49,3 +49,34 @@
Thread.sleep(rate)
}
}
+
+/**
+ * Ensures that [conditionFunction] is true with a failed assertion if it is not within [window]
+ * ms.
+ *
+ * The condition function can perform additional logic (for example, logging or attempting to make
+ * the condition become true).
+ *
+ * @param conditionFunction function which takes the attempt count & returns whether the condition
+ * is met
+ */
+internal fun ensure(
+ description: String? = null,
+ window: Long = 30000L,
+ rate: Long = 50L,
+ conditionFunction: (count: Int) -> Boolean
+) {
+ var waited = 0L
+ var count = 0
+ while (waited <= window) {
+ assertWithMessage("Condition failed within $window ms: $description").that(
+ conditionFunction(
+ count
+ )
+ ).isTrue()
+ waited += rate
+ count++
+ Log.i(TAG, "Ensuring $description ($waited/$window) #$count")
+ Thread.sleep(rate)
+ }
+}