diff options
875 files changed, 17055 insertions, 7779 deletions
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg index b42f7bc0ca94..e8571757c6f7 100644 --- a/PREUPLOAD.cfg +++ b/PREUPLOAD.cfg @@ -1,6 +1,7 @@ [Builtin Hooks] clang_format = true bpfmt = true +ktfmt = true [Builtin Hooks Options] # Only turn on clang-format check for the following subfolders. @@ -17,6 +18,7 @@ clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp tests/ tools/ bpfmt = -d +ktfmt = --kotlinlang-style --include-dirs=services/permission,packages/SystemUI [Hook Scripts] checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT} @@ -25,9 +27,10 @@ hidden_api_txt_checksorted_hook = ${REPO_ROOT}/tools/platform-compat/hiddenapi/c hidden_api_txt_exclude_hook = ${REPO_ROOT}/frameworks/base/tools/hiddenapi/exclude.sh ${PREUPLOAD_COMMIT} ${REPO_ROOT} -ktfmt_hook = ${REPO_ROOT}/external/ktfmt/ktfmt.py --check -i ${REPO_ROOT}/frameworks/base/ktfmt_includes.txt ${PREUPLOAD_FILES} - ktlint_hook = ${REPO_ROOT}/prebuilts/ktlint/ktlint.py --no-verify-format -f ${PREUPLOAD_FILES} # This flag check hook runs only for "packages/SystemUI" subdirectory. If you want to include this check for other subdirectories, please modify flag_check.py. flag_hook = ${REPO_ROOT}/frameworks/base/packages/SystemUI/flag_check.py --msg=${PREUPLOAD_COMMIT_MESSAGE} --files=${PREUPLOAD_FILES} --project=${REPO_PATH} + +[Tool Paths] +ktfmt = ${REPO_ROOT}/prebuilts/build-tools/common/framework/ktfmt.jar diff --git a/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java b/apct-tests/perftests/multiuser/src/android/multiuser/BenchmarkRunner.java index 459c2868e9ba..515ddc8d1d49 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.annotation.Nullable; 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 @@ public class BenchmarkRunner { 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 @@ public class BenchmarkRunner { * to avoid unnecessary waiting. */ public void resumeTiming() { - waitCoolDownPeriod(); + ShellHelper.runShellCommand("am wait-for-broadcast-idle --flush-broadcast-loopers"); resumeTimer(); } @@ -165,56 +162,4 @@ public class BenchmarkRunner { } 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 98ab0c290e40..762e2af09cd3 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 @@ public class UserLifecycleTests { } } + /** 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 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); + waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -238,6 +254,7 @@ public class UserLifecycleTests { mRunner.pauseTiming(); final int userId = createUserNoFlags(); + waitForBroadcastIdle(); runThenWaitForBroadcasts(userId, () -> { mRunner.resumeTiming(); Log.i(TAG, "Starting timer"); @@ -292,6 +309,9 @@ public class UserLifecycleTests { preStartUser(userId, numberOfIterationsToSkip); + waitForBroadcastIdle(); + waitCoolDownPeriod(); + runThenWaitForBroadcasts(userId, () -> { mRunner.resumeTiming(); Log.i(TAG, "Starting timer"); @@ -333,6 +353,9 @@ public class UserLifecycleTests { while (mRunner.keepRunning()) { mRunner.pauseTiming(); + waitForBroadcastIdle(); + waitCoolDownPeriod(); + runThenWaitForBroadcasts(userId, () -> { mRunner.resumeTiming(); Log.i(TAG, "Starting timer"); @@ -397,6 +420,7 @@ public class UserLifecycleTests { while (mRunner.keepRunning()) { mRunner.pauseTiming(); + waitCoolDownPeriod(); mRunner.resumeTiming(); Log.i(TAG, "Starting timer"); @@ -430,6 +454,7 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); + waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -441,7 +466,6 @@ public class UserLifecycleTests { mRunner.pauseTiming(); final int startUser = mAm.getCurrentUser(); final int userId = createUserNoFlags(); - mRunner.resumeTiming(); Log.i(TAG, "Starting timer"); @@ -455,6 +479,27 @@ public class UserLifecycleTests { } } + /** 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 @@ public class UserLifecycleTests { mRunner.pauseTiming(); final int startUser = mAm.getCurrentUser(); final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ true); - mRunner.resumeTiming(); Log.i(TAG, "Starting timer"); @@ -492,6 +536,7 @@ public class UserLifecycleTests { while (mRunner.keepRunning()) { mRunner.pauseTiming(); + waitCoolDownPeriod(); Log.d(TAG, "Starting timer"); mRunner.resumeTiming(); @@ -517,6 +562,7 @@ public class UserLifecycleTests { /* useStaticWallpaper */true); while (mRunner.keepRunning()) { mRunner.pauseTiming(); + waitCoolDownPeriod(); Log.d(TAG, "Starting timer"); mRunner.resumeTiming(); @@ -560,6 +606,7 @@ public class UserLifecycleTests { final int testUser = initializeNewUserAndSwitchBack(/* stopNewUser */ false); while (mRunner.keepRunning()) { mRunner.pauseTiming(); + waitCoolDownPeriod(); Log.d(TAG, "Starting timer"); mRunner.resumeTiming(); @@ -567,6 +614,7 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); + waitForBroadcastIdle(); switchUserNoCheck(startUser); mRunner.resumeTimingForNextIteration(); } @@ -583,6 +631,7 @@ public class UserLifecycleTests { /* useStaticWallpaper */ true); while (mRunner.keepRunning()) { mRunner.pauseTiming(); + waitCoolDownPeriod(); Log.d(TAG, "Starting timer"); mRunner.resumeTiming(); @@ -590,6 +639,7 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); + waitForBroadcastIdle(); switchUserNoCheck(startUser); mRunner.resumeTimingForNextIteration(); } @@ -625,11 +675,13 @@ public class UserLifecycleTests { @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 @@ public class UserLifecycleTests { 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 @@ public class UserLifecycleTests { 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 @@ public class UserLifecycleTests { switchUser(userId); }, Intent.ACTION_MEDIA_MOUNTED); - mRunner.waitCoolDownPeriod(); + waitForBroadcastIdle(); mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> { runThenWaitForBroadcasts(userId, () -> { mRunner.resumeTiming(); @@ -729,7 +781,7 @@ public class UserLifecycleTests { switchUser(userId); }, Intent.ACTION_MEDIA_MOUNTED); - mRunner.waitCoolDownPeriod(); + waitCoolDownPeriod(); mUserSwitchWaiter.runThenWaitUntilSwitchCompleted(startUser, () -> { runThenWaitForBroadcasts(userId, () -> { mRunner.resumeTiming(); @@ -775,6 +827,7 @@ public class UserLifecycleTests { Log.d(TAG, "Stopping timer"); attestTrue("Failed creating profile " + userId, mUm.isManagedProfile(userId)); removeUser(userId); + waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -815,6 +868,7 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); + waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -859,6 +913,7 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); + waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } removeUser(userId); @@ -910,6 +965,7 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); + waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -974,6 +1030,7 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); + waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -1014,6 +1071,7 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); + waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -1066,6 +1124,7 @@ public class UserLifecycleTests { mRunner.pauseTiming(); Log.d(TAG, "Stopping timer"); removeUser(userId); + waitCoolDownPeriod(); mRunner.resumeTimingForNextIteration(); } } @@ -1105,6 +1164,7 @@ public class UserLifecycleTests { runThenWaitForBroadcasts(userId, () -> { startUserInBackgroundAndWaitForUnlock(userId); }, Intent.ACTION_MEDIA_MOUNTED); + waitCoolDownPeriod(); mRunner.resumeTiming(); Log.d(TAG, "Starting timer"); @@ -1220,7 +1280,6 @@ public class UserLifecycleTests { * 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 @@ public class UserLifecycleTests { */ private void stopUserAfterWaitingForBroadcastIdle(int userId) throws RemoteException { - mRunner.waitCoolDownPeriod(); + waitForBroadcastIdle(); stopUser(userId); } @@ -1379,8 +1438,6 @@ public class UserLifecycleTests { */ private void runThenWaitForBroadcasts(int userId, FunctionalUtils.ThrowingRunnable runnable, String... actions) { - mRunner.waitCoolDownPeriod(); - final String unreceivedAction = mBroadcastWaiter.runThenWaitForBroadcasts(userId, runnable, actions); @@ -1481,4 +1538,28 @@ public class UserLifecycleTests { 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/apct-tests/perftests/permission/src/android/perftests/permission/PermissionManagerPerfTest.kt b/apct-tests/perftests/permission/src/android/perftests/permission/PermissionManagerPerfTest.kt new file mode 100644 index 000000000000..8b8036ecc3a1 --- /dev/null +++ b/apct-tests/perftests/permission/src/android/perftests/permission/PermissionManagerPerfTest.kt @@ -0,0 +1,75 @@ +/* + * 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.perftests.permission + +import android.Manifest +import android.companion.virtual.VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT +import android.content.Context +import android.perftests.utils.PerfStatusReporter +import android.permission.PermissionManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import com.android.compatibility.common.util.AdoptShellPermissionsRule +import com.android.compatibility.common.util.SystemUtil.runShellCommand +import org.junit.After +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class PermissionManagerPerfTest { + @get:Rule var perfStatusReporter = PerfStatusReporter() + @get:Rule + val mAdoptShellPermissionsRule = + AdoptShellPermissionsRule( + InstrumentationRegistry.getInstrumentation().getUiAutomation(), + Manifest.permission.INSTALL_PACKAGES, + Manifest.permission.DELETE_PACKAGES, + Manifest.permission.GRANT_RUNTIME_PERMISSIONS, + Manifest.permission.MANAGE_ONE_TIME_PERMISSION_SESSIONS, + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, + ) + + val context: Context = InstrumentationRegistry.getInstrumentation().targetContext + val permissionManager = context.getSystemService(PermissionManager::class.java)!! + + @Before + fun setup() { + val apkPath = "$APK_DIR${APK_NAME}.apk" + runShellCommand("pm install -tg $apkPath") + } + + @After + fun cleanup() { + runShellCommand("pm uninstall $PKG_NAME") + } + + @Test + fun testGetAllPermissionStates() { + val benchmarkState = perfStatusReporter.benchmarkState + while (benchmarkState.keepRunning()) { + permissionManager.getAllPermissionStates(PKG_NAME, PERSISTENT_DEVICE_ID_DEFAULT) + } + } + + companion object { + private const val APK_DIR = "/data/local/tmp/perftests/" + private const val APK_NAME = "UsePermissionApp0" + private const val PKG_NAME = "android.perftests.appenumeration0" + } +} diff --git a/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt b/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt index 13e67e34a8d9..1139835d3e7b 100644 --- a/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt +++ b/apct-tests/perftests/permission/src/android/perftests/permission/PermissionServicePerfTest.kt @@ -36,6 +36,7 @@ import java.io.IOException import java.io.InputStreamReader import java.util.concurrent.TimeUnit import java.util.function.BiConsumer +import org.junit.Ignore @RunWith(AndroidJUnit4::class) class PermissionServicePerfTest { @@ -48,6 +49,7 @@ class PermissionServicePerfTest { val mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation() @Test + @Ignore("Fails to capture duration, results in infinite loop and execution timeout") fun testInstallPackages() { mUiAutomation.executeShellCommand(COMMAND_TRACE_START) eventually { assertThat(Trace.isTagEnabled(TRACE_TAG)).isTrue() } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 96315ebccc49..50d97cf0626f 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -14435,6 +14435,7 @@ package android.telephony { 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 fd9600c1f87f..65628d32e583 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.AccessibilityNodeInfo.AccessibilityAction; 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 @@ public abstract class AccessibilityService extends Service { /** 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 abstract class AccessibilityService extends Service { public static final String KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP = "screenshot_timestamp"; - /** * Annotations for result codes of attaching accessibility overlays. * @@ -837,6 +841,13 @@ public abstract class AccessibilityService extends Service { 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 @@ public abstract class AccessibilityService extends Service { 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 @@ public abstract class AccessibilityService extends Service { // 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 @@ public abstract class AccessibilityService extends Service { } } + 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 @@ public abstract class AccessibilityService extends Service { } @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 @@ public abstract class AccessibilityService extends Service { }); } + @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 3bc61e560d8c..f1479ef79dd9 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceClient.aidl @@ -48,6 +48,8 @@ import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; 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 713d8e5dd12f..149e7194a43b 100644 --- a/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl +++ b/core/java/android/accessibilityservice/IAccessibilityServiceConnection.aidl @@ -130,6 +130,9 @@ interface IAccessibilityServiceConnection { 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 2887d228e1b6..fa8fe3bf5458 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1755,6 +1755,12 @@ public class ActivityManager { 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 @@ public class ActivityManager { 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 @@ public class ActivityManager { @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 @@ public class ActivityManager { @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 @@ public class ActivityManager { */ @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 @@ public class ActivityManager { */ @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 @@ public class ActivityManager { @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 @@ public class ActivityManager { */ @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 @@ public class ActivityManager { 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 @@ public class ActivityManager { mStatusBarColor = statusBarColor; mNavigationBarColor = navigationBarColor; mSystemBarsAppearance = systemBarsAppearance; + mTopOpaqueSystemBarsAppearance = topOpaqueSystemBarsAppearance; mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent; mEnsureNavigationBarContrastWhenTransparent = ensureNavigationBarContrastWhenTransparent; @@ -1994,6 +2002,7 @@ public class ActivityManager { mStatusBarColor = other.mStatusBarColor; mNavigationBarColor = other.mNavigationBarColor; mSystemBarsAppearance = other.mSystemBarsAppearance; + mTopOpaqueSystemBarsAppearance = other.mTopOpaqueSystemBarsAppearance; mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent; mEnsureNavigationBarContrastWhenTransparent = other.mEnsureNavigationBarContrastWhenTransparent; @@ -2026,6 +2035,9 @@ public class ActivityManager { if (other.mSystemBarsAppearance != 0) { mSystemBarsAppearance = other.mSystemBarsAppearance; } + if (other.mTopOpaqueSystemBarsAppearance != 0) { + mTopOpaqueSystemBarsAppearance = other.mTopOpaqueSystemBarsAppearance; + } mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent; mEnsureNavigationBarContrastWhenTransparent = @@ -2305,6 +2317,14 @@ public class ActivityManager { /** * @hide */ + @Appearance + public int getTopOpaqueSystemBarsAppearance() { + return mTopOpaqueSystemBarsAppearance; + } + + /** + * @hide + */ public void setEnsureStatusBarContrastWhenTransparent( boolean ensureStatusBarContrastWhenTransparent) { mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent; @@ -2320,6 +2340,13 @@ public class ActivityManager { /** * @hide */ + public void setTopOpaqueSystemBarsAppearance(int topOpaqueSystemBarsAppearance) { + mTopOpaqueSystemBarsAppearance = topOpaqueSystemBarsAppearance; + } + + /** + * @hide + */ public boolean getEnsureNavigationBarContrastWhenTransparent() { return mEnsureNavigationBarContrastWhenTransparent; } @@ -2442,6 +2469,7 @@ public class ActivityManager { 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 @@ public class ActivityManager { 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 @@ public class ActivityManager { + " resizeMode: " + ActivityInfo.resizeModeToString(mResizeMode) + " minWidth: " + mMinWidth + " minHeight: " + mMinHeight + " colorBackgrounFloating: " + mColorBackgroundFloating - + " systemBarsAppearance: " + mSystemBarsAppearance; + + " systemBarsAppearance: " + mSystemBarsAppearance + + " topOpaqueSystemBarsAppearance: " + mTopOpaqueSystemBarsAppearance; } @Override @@ -2519,6 +2549,7 @@ public class ActivityManager { 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 @@ public class ActivityManager { && 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 7dbf270672f8..76c1ed619510 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -355,7 +355,7 @@ public final class ActivityThread extends ClientTransactionHandler 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 @@ public final class ActivityThread extends ClientTransactionHandler @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 c0f723241c82..5956e2bde242 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -2617,6 +2617,9 @@ public class ApplicationPackageManager extends PackageManager { 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 4c839f1762cb..fc3bb0288d67 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -114,7 +114,7 @@ import com.android.internal.annotations.VisibleForTesting; 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable } /** + * 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable } /** + * 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 @@ public class Notification implements Parcelable 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 @@ public class Notification implements Parcelable // 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 348d4d8fd809..273a79efb591 100644 --- a/core/java/android/app/UiAutomation.java +++ b/core/java/android/app/UiAutomation.java @@ -1969,6 +1969,11 @@ public final class UiAutomation { } @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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 44444b5b1471..67752f2b1498 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -62,6 +62,7 @@ import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_PROVISION import static android.app.admin.flags.Flags.FLAG_HEADLESS_DEVICE_OWNER_SINGLE_USER_ENABLED; import static android.app.admin.flags.Flags.FLAG_SECURITY_LOG_V2_ENABLED; import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled; +import static android.app.admin.flags.Flags.onboardingConsentlessBugreports; import static android.app.admin.flags.Flags.FLAG_IS_MTE_POLICY_ENFORCED; import static android.content.Intent.LOCAL_FLAG_FROM_SYSTEM; import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_1; @@ -17626,6 +17627,17 @@ public class DevicePolicyManager { return onboardingBugreportV2Enabled(); } + // TODO(b/308755220): Remove once the build is finalised. + /** + * Returns true if the flag for consentless bugreports is enabled. + * + * @hide + */ + @UnsupportedAppUsage + public boolean isOnboardingConsentlessBugreportFlagEnabled() { + return onboardingConsentlessBugreports(); + } + /** * Returns the subscription ids of all subscriptions which were downloaded by the calling * admin. diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 55c3bb60e9c7..2d783177e0d7 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -52,13 +52,31 @@ flag { 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" @@ -174,3 +192,10 @@ flag { 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 1c8e497edd0a..eb31db18473f 100644 --- a/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java +++ b/core/java/android/app/servertransaction/WindowStateInsetsControlChangeItem.java @@ -27,6 +27,8 @@ import android.view.IWindow; import android.view.InsetsSourceControl; import android.view.InsetsState; +import com.android.internal.annotations.VisibleForTesting; + import java.util.Objects; /** @@ -38,7 +40,9 @@ public class WindowStateInsetsControlChangeItem extends WindowStateTransactionIt 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 @@ public class WindowStateInsetsControlChangeItem extends WindowStateTransactionIt // 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 @@ public class WindowStateInsetsControlChangeItem extends WindowStateTransactionIt } 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 374be6fd2272..18cfca686107 100644 --- a/core/java/android/appwidget/flags.aconfig +++ b/core/java/android/appwidget/flags.aconfig @@ -40,3 +40,13 @@ flag { 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 ed5d66227574..1e7815329f3b 100644 --- a/core/java/android/companion/virtual/flags/flags.aconfig +++ b/core/java/android/companion/virtual/flags/flags.aconfig @@ -64,3 +64,13 @@ flag { 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/Intent.java b/core/java/android/content/Intent.java index c8cae822570e..02d62a2a402b 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -8157,6 +8157,9 @@ public class Intent implements Parcelable, Cloneable { int eq = uri.indexOf('=', i); if (eq < 0) eq = i-1; int semi = uri.indexOf(';', i); + if (semi < 0) { + throw new URISyntaxException(uri, "uri end not found"); + } String value = eq < semi ? Uri.decode(uri.substring(eq + 1, semi)) : ""; // action diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 270fc32a4e32..821034aaf204 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -2431,6 +2431,7 @@ public class PackageInstaller { 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 @@ public class PackageInstaller { } 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 @@ public class PackageInstaller { userActionIntent, new UserHandle(mUserId)); } catch (ParcelableException e) { e.maybeRethrow(PackageManager.NameNotFoundException.class); + throw new RuntimeException(e); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -3465,8 +3468,12 @@ public class PackageInstaller { * Android S ({@link android.os.Build.VERSION_CODES#S API 31})</li> * <li>{@link android.os.Build.VERSION_CODES#R API 30} or higher on * Android T ({@link android.os.Build.VERSION_CODES#TIRAMISU API 33})</li> - * <li>{@link android.os.Build.VERSION_CODES#S API 31} or higher <b>after</b> - * Android T ({@link android.os.Build.VERSION_CODES#TIRAMISU API 33})</li> + * <li>{@link android.os.Build.VERSION_CODES#S API 31} or higher on + * Android U ({@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE API 34}) + * </li> + * <li>{@link android.os.Build.VERSION_CODES#TIRAMISU API 33} or higher on + * Android V ({@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM API 35}) + * </li> * </ul> * </li> * <li>The installer is: diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index e2a131c0d527..5668c540f5bc 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -140,6 +140,16 @@ flag { } } +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 e3780edcd7da..f54be00c9e69 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -733,7 +733,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * 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 @@ public final class SQLiteDatabase extends SQLiteClosable { * 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 @@ public final class SQLiteDatabase extends SQLiteClosable { * } * </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 4284ad09e251..047d1fa4f49a 100644 --- a/core/java/android/hardware/biometrics/flags.aconfig +++ b/core/java/android/hardware/biometrics/flags.aconfig @@ -32,3 +32,10 @@ flag { 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 342479bc159e..3cc87ea9d359 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1492,10 +1492,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * 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 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <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 c82e7e8c12ef..938636f7e8e5 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -2684,35 +2684,39 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * <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 1460515c2438..4406a419c317 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2977,35 +2977,39 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <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 9eabc8d53bb3..53771e38e4a5 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -963,11 +963,10 @@ public final class InputManager { * 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/os/OWNERS b/core/java/android/os/OWNERS index 9f3364feda84..04d49708b6ef 100644 --- a/core/java/android/os/OWNERS +++ b/core/java/android/os/OWNERS @@ -107,4 +107,8 @@ per-file SystemConfigManager.java = file:/PACKAGE_MANAGER_OWNERS per-file ProfilingServiceManager.java = file:/PERFORMANCE_OWNERS # Memory -per-file OomKillRecord.java = file:/MEMORY_OWNERS
\ No newline at end of file +per-file OomKillRecord.java = file:/MEMORY_OWNERS + +# MessageQueue +per-file MessageQueue.java = mfasheh@google.com, shayba@google.com +per-file Message.java = mfasheh@google.com, shayba@google.com diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index f357ebff9878..793321254a09 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -19930,6 +19930,12 @@ public final class Settings { 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/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig index c7d951b99cce..56d3669ac50c 100644 --- a/core/java/android/security/responsible_apis_flags.aconfig +++ b/core/java/android/security/responsible_apis_flags.aconfig @@ -45,3 +45,10 @@ flag { description: "Do not allow intents without an action to match any intent filters" bug: "293560872" } + +flag { + name: "asm_opt_system_into_enforcement" + namespace: "responsible_apis" + description: "Opt the system into enforcement of BAL" + bug: "339403750" +} diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 71066ac7ac39..3f9c819cd62f 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -1276,13 +1276,22 @@ public class DreamService extends Service implements Window.Callback { }); } + /** + * 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 c674968bba8a..0dec13ff0c02 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -18,9 +18,10 @@ package android.text; 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { mSpacingMult = spacingmult; mSpacingAdd = spacingadd; mSpannedText = text instanceof Spanned; + initSpanColors(); } /** @@ -643,20 +659,20 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 @@ public abstract class Layout { 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 000000000000..fcd242b62700 --- /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 da8c273fd14e..b4cb68c242b3 100644 --- a/core/java/android/tracing/perfetto/InitArguments.java +++ b/core/java/android/tracing/perfetto/InitArguments.java @@ -26,6 +26,7 @@ import java.lang.annotation.RetentionPolicy; */ public class InitArguments { public final @PerfettoBackend int backends; + public final int shmemSizeHintKb; /** * @hide @@ -44,11 +45,21 @@ public class InitArguments { // 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 a1b3eb754157..13582e8742c3 100644 --- a/core/java/android/tracing/perfetto/Producer.java +++ b/core/java/android/tracing/perfetto/Producer.java @@ -27,8 +27,8 @@ public class Producer { * @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 ba1915c77060..15b0c13de524 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -2263,6 +2263,7 @@ public final class Display { 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 da86e2dc2383..8b9d876f6bf1 100644 --- a/core/java/android/view/DisplayInfo.java +++ b/core/java/android/view/DisplayInfo.java @@ -213,7 +213,7 @@ public final class DisplayInfo implements Parcelable { /** * 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 a9846fb5751f..eec805b74b07 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -303,7 +303,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } /** 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 class InsetsController implements WindowInsetsController, InsetsAnimation 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 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation 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 6c670f5d6934..fdb2a6ee1791 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 static com.android.internal.annotations.VisibleForTesting.Visibility.PACK 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 @@ public class InsetsSourceConsumer { 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 @@ public class InsetsSourceConsumer { 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 4e5cb58a00b5..487214c5c33a 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -269,22 +269,66 @@ public class InsetsSourceControl implements Parcelable { 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/MotionEvent.java b/core/java/android/view/MotionEvent.java index 6db40bf6e0b8..79a9f2d38ad9 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -682,7 +682,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { * <li>For a touch screen or touch pad, reports the approximate size of the contact area in * relation to the maximum detectable size for the device. The value is normalized * to a range from 0 (smallest detectable size) to 1 (largest detectable size), - * although it is not a linear scale. This value is of limited use. + * although it is not a linear scale. The value of size can be used to + * determine fat touch events. * To obtain calibrated size information, use * {@link #AXIS_TOUCH_MAJOR} or {@link #AXIS_TOOL_MAJOR}. * </ul> @@ -2795,13 +2796,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { } /** - * Returns the current pressure of this event for the given pointer - * <em>index</em> (use {@link #getPointerId(int)} to find the pointer - * identifier for this index). - * The pressure generally - * ranges from 0 (no pressure at all) to 1 (normal pressure), however - * values higher than 1 may be generated depending on the calibration of - * the input device. + * Returns the value of {@link #AXIS_PRESSURE} for the given pointer <em>index</em>. + * * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 * (the first pointer that is down) to {@link #getPointerCount()}-1. * @@ -2812,14 +2808,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { } /** - * Returns a scaled value of the approximate size for the given pointer - * <em>index</em> (use {@link #getPointerId(int)} to find the pointer - * identifier for this index). - * This represents some approximation of the area of the screen being - * pressed; the actual value in pixels corresponding to the - * touch is normalized with the device specific range of values - * and scaled to a value between 0 and 1. The value of size can be used to - * determine fat touch events. + * Returns the value of {@link #AXIS_SIZE} for the given pointer <em>index</em>. + * * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 * (the first pointer that is down) to {@link #getPointerCount()}-1. * @@ -2830,10 +2820,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { } /** - * Returns the length of the major axis of an ellipse that describes the touch - * area at the point of contact for the given pointer - * <em>index</em> (use {@link #getPointerId(int)} to find the pointer - * identifier for this index). + * Returns the value of {@link #AXIS_TOUCH_MAJOR} for the given pointer <em>index</em>. + * * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 * (the first pointer that is down) to {@link #getPointerCount()}-1. * @@ -2844,10 +2832,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { } /** - * Returns the length of the minor axis of an ellipse that describes the touch - * area at the point of contact for the given pointer - * <em>index</em> (use {@link #getPointerId(int)} to find the pointer - * identifier for this index). + * Returns the value of {@link #AXIS_TOUCH_MINOR} for the given pointer <em>index</em>. + * * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 * (the first pointer that is down) to {@link #getPointerCount()}-1. * @@ -2858,12 +2844,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { } /** - * Returns the length of the major axis of an ellipse that describes the size of - * the approaching tool for the given pointer - * <em>index</em> (use {@link #getPointerId(int)} to find the pointer - * identifier for this index). - * The tool area represents the estimated size of the finger or pen that is - * touching the device independent of its actual touch area at the point of contact. + * Returns the value of {@link #AXIS_TOOL_MAJOR} for the given pointer <em>index</em>. + * * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 * (the first pointer that is down) to {@link #getPointerCount()}-1. * @@ -2874,12 +2856,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { } /** - * Returns the length of the minor axis of an ellipse that describes the size of - * the approaching tool for the given pointer - * <em>index</em> (use {@link #getPointerId(int)} to find the pointer - * identifier for this index). - * The tool area represents the estimated size of the finger or pen that is - * touching the device independent of its actual touch area at the point of contact. + * Returns the value of {@link #AXIS_TOOL_MINOR} for the given pointer <em>index</em>. + * * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 * (the first pointer that is down) to {@link #getPointerCount()}-1. * @@ -2890,15 +2868,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { } /** - * Returns the orientation of the touch area and tool area in radians clockwise from vertical - * for the given pointer <em>index</em> (use {@link #getPointerId(int)} to find the pointer - * identifier for this index). - * An angle of 0 radians indicates that the major axis of contact is oriented - * upwards, is perfectly circular or is of unknown orientation. A positive angle - * indicates that the major axis of contact is oriented to the right. A negative angle - * indicates that the major axis of contact is oriented to the left. - * The full range is from -PI/2 radians (finger pointing fully left) to PI/2 radians - * (finger pointing fully right). + * Returns the value of {@link #AXIS_ORIENTATION} for the given pointer <em>index</em>. + * * @param pointerIndex Raw index of pointer to retrieve. Value may be from 0 * (the first pointer that is down) to {@link #getPointerCount()}-1. * diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 1cb276568244..5e3f09ac895c 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1984,9 +1984,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback, public @interface ContentSensitivity {} /** - * Automatically determine whether a view displays sensitive content. For example, available - * autofill hints (or some other signal) can be used to determine if this view - * displays sensitive content. + * Content sensitivity is determined by the framework. The framework uses a heuristic to + * determine if this view displays sensitive content. + * Autofill hints i.e. {@link #getAutofillHints()} are used in the heuristic + * to determine if this view should be considered as a sensitive view. + * <p> + * {@link #AUTOFILL_HINT_USERNAME}, + * {@link #AUTOFILL_HINT_PASSWORD}, + * {@link #AUTOFILL_HINT_CREDIT_CARD_NUMBER}, + * {@link #AUTOFILL_HINT_CREDIT_CARD_SECURITY_CODE}, + * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DATE}, + * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_DAY}, + * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_MONTH}, + * {@link #AUTOFILL_HINT_CREDIT_CARD_EXPIRATION_YEAR} + * are considered sensitive hints by the framework, and the list may include more hints + * in the future. + * + * <p> The window hosting a sensitive view will be marked as secure during an active media + * projection session. This would be equivalent to applying + * {@link android.view.WindowManager.LayoutParams#FLAG_SECURE} to the window. * * @see #getContentSensitivity() */ @@ -1996,6 +2012,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * The view displays sensitive content. * + * <p> The window hosting a sensitive view will be marked as secure during an active media + * projection session. This would be equivalent to applying + * {@link android.view.WindowManager.LayoutParams#FLAG_SECURE} to the window. + * * @see #getContentSensitivity() */ @FlaggedApi(FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API) @@ -10548,9 +10568,13 @@ public class View implements Drawable.Callback, KeyEvent.Callback, /** * Sets content sensitivity mode to determine whether this view displays sensitive content - * (e.g. username, password etc.). The system may improve user privacy i.e. hide content + * (e.g. username, password etc.). The system will improve user privacy i.e. hide content * drawn by a sensitive view from screen sharing and recording. * + * <p> The window hosting a sensitive view will be marked as secure during an active media + * projection session. This would be equivalent to applying + * {@link android.view.WindowManager.LayoutParams#FLAG_SECURE} to the window. + * * @param mode {@link #CONTENT_SENSITIVITY_AUTO}, {@link #CONTENT_SENSITIVITY_NOT_SENSITIVE} * or {@link #CONTENT_SENSITIVITY_SENSITIVE} */ @@ -10574,8 +10598,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * {@link #setContentSensitivity(int)}. */ @FlaggedApi(FLAG_SENSITIVE_CONTENT_APP_PROTECTION_API) - public @ContentSensitivity - final int getContentSensitivity() { + public @ContentSensitivity final int getContentSensitivity() { return (mPrivateFlags4 & PFLAG4_CONTENT_SENSITIVITY_MASK) >> PFLAG4_CONTENT_SENSITIVITY_SHIFT; } @@ -23705,12 +23728,6 @@ public class View implements Drawable.Callback, KeyEvent.Callback, 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 +23744,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback, draw(canvas); } } + + // For VRR to vote the preferred frame rate + if (sToolkitSetFrameRateReadOnlyFlagValue + && sToolkitFrameRateViewEnablingReadOnlyFlagValue) { + votePreferredFrameRate(); + } } finally { renderNode.endRecording(); setDisplayListProperties(renderNode); @@ -34061,10 +34084,14 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } 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 139285a44817..fd407d6ec3ed 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -2276,6 +2276,29 @@ public final class ViewRootImpl implements ViewParent, 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 final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, 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 @@ public final class ViewRootImpl implements ViewParent, 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 86e5bea46882..1af9387e6fbd 100644 --- a/core/java/android/view/ViewStructure.java +++ b/core/java/android/view/ViewStructure.java @@ -90,6 +90,19 @@ public abstract class ViewStructure { 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/WindowManager.java b/core/java/android/view/WindowManager.java index 0f54940ba0e5..42bf420b9812 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -2801,6 +2801,10 @@ public interface WindowManager extends ViewManager { * it from appearing in screenshots or from being viewed on non-secure * displays. * + * <p>See {@link android.view.View#setContentSensitivity(int)}, a window hosting + * a sensitive view will be marked as secure during media projection, preventing + * it from being viewed on non-secure displays and during screen share. + * * <p>See {@link android.view.Display#FLAG_SECURE} for more details about * secure surfaces and secure displays. */ diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java index 12e08148a651..1fe8180aa7b2 100644 --- a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java +++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java @@ -302,6 +302,10 @@ public abstract class AccessibilityDisplayProxy { } @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 ab7b2261dc17..edf33875b765 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 @@ flag { 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/widget/RemoteViewsService.java b/core/java/android/widget/RemoteViewsService.java index 07d6acbe38a8..c79eac605e64 100644 --- a/core/java/android/widget/RemoteViewsService.java +++ b/core/java/android/widget/RemoteViewsService.java @@ -132,7 +132,8 @@ public abstract class RemoteViewsService extends Service { RemoteViews.RemoteCollectionItems items = new RemoteViews.RemoteCollectionItems .Builder().build(); Parcel capSizeTestParcel = Parcel.obtain(); - capSizeTestParcel.allowSquashing(); + // restore allowSquashing to reduce the noise in error messages + boolean prevAllowSquashing = capSizeTestParcel.allowSquashing(); try { RemoteViews.RemoteCollectionItems.Builder itemsBuilder = @@ -154,6 +155,7 @@ public abstract class RemoteViewsService extends Service { items = itemsBuilder.build(); } finally { + capSizeTestParcel.restoreAllowSquashing(prevAllowSquashing); // Recycle the parcel capSizeTestParcel.recycle(); } diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java index 1bd921b339f6..d5398e6268dc 100644 --- a/core/java/android/window/ClientWindowFrames.java +++ b/core/java/android/window/ClientWindowFrames.java @@ -56,7 +56,16 @@ public class ClientWindowFrames implements Parcelable { 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 @@ public class ClientWindowFrames implements Parcelable { 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 f928f509bdb6..4c8bad6d0aff 100644 --- a/core/java/android/window/SnapshotDrawerUtils.java +++ b/core/java/android/window/SnapshotDrawerUtils.java @@ -77,6 +77,14 @@ public class SnapshotDrawerUtils { 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 @@ public class SnapshotDrawerUtils { 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/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig index 983f46c58c4b..5b99ff9703e0 100644 --- a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig +++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig @@ -73,6 +73,16 @@ flag { } flag { + name: "immersive_app_repositioning" + namespace: "large_screen_experiences_app_compat" + description: "Fix immersive apps changing size when repositioning" + bug: "334076352" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "camera_compat_for_freeform" namespace: "large_screen_experiences_app_compat" description: "Whether to apply Camera Compat treatment to fixed-orientation apps in freeform windowing mode" diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig index 21aa4800237c..9e69f8910bab 100644 --- a/core/java/android/window/flags/windowing_sdk.aconfig +++ b/core/java/android/window/flags/windowing_sdk.aconfig @@ -158,4 +158,14 @@ flag { metadata { purpose: PURPOSE_BUGFIX } -}
\ No newline at end of file +} + +flag { + namespace: "windowing_sdk" + name: "move_animation_options_to_change" + description: "Move AnimationOptions from TransitionInfo to each Change" + bug: "327332488" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/com/android/internal/graphics/ColorUtils.java b/core/java/com/android/internal/graphics/ColorUtils.java index dff9551c0c07..f72a5ca2bffb 100644 --- a/core/java/com/android/internal/graphics/ColorUtils.java +++ b/core/java/com/android/internal/graphics/ColorUtils.java @@ -392,7 +392,7 @@ public final class ColorUtils { * Convert RGB components to its CIE Lab representative components. * * <ul> - * <li>outLab[0] is L [0 ...1)</li> + * <li>outLab[0] is L [0 ...100)</li> * <li>outLab[1] is a [-128...127)</li> * <li>outLab[2] is b [-128...127)</li> * </ul> @@ -474,7 +474,7 @@ public final class ColorUtils { * 2° Standard Observer (1931).</p> * * <ul> - * <li>outLab[0] is L [0 ...1)</li> + * <li>outLab[0] is L [0 ...100)</li> * <li>outLab[1] is a [-128...127)</li> * <li>outLab[2] is b [-128...127)</li> * </ul> diff --git a/core/java/com/android/internal/os/TimeoutRecord.java b/core/java/com/android/internal/os/TimeoutRecord.java index 1f4abc19571a..e9a8d4b75f16 100644 --- a/core/java/com/android/internal/os/TimeoutRecord.java +++ b/core/java/com/android/internal/os/TimeoutRecord.java @@ -45,7 +45,6 @@ public class TimeoutRecord { TimeoutKind.APP_REGISTERED, TimeoutKind.SHORT_FGS_TIMEOUT, TimeoutKind.JOB_SERVICE, - TimeoutKind.FGS_TIMEOUT, }) @Retention(RetentionPolicy.SOURCE) @@ -60,7 +59,6 @@ public class TimeoutRecord { int SHORT_FGS_TIMEOUT = 8; int JOB_SERVICE = 9; int APP_START = 10; - int FGS_TIMEOUT = 11; } /** Kind of timeout, e.g. BROADCAST_RECEIVER, etc. */ @@ -188,12 +186,6 @@ public class TimeoutRecord { return TimeoutRecord.endingNow(TimeoutKind.SHORT_FGS_TIMEOUT, reason); } - /** Record for a "foreground service" timeout. */ - @NonNull - public static TimeoutRecord forFgsTimeout(String reason) { - return TimeoutRecord.endingNow(TimeoutKind.FGS_TIMEOUT, reason); - } - /** Record for a job related timeout. */ @NonNull public static TimeoutRecord forJobService(String reason) { diff --git a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java index e11067dbd722..f62ff38528f7 100644 --- a/core/java/com/android/internal/os/anr/AnrLatencyTracker.java +++ b/core/java/com/android/internal/os/anr/AnrLatencyTracker.java @@ -22,7 +22,6 @@ import static com.android.internal.os.TimeoutRecord.TimeoutKind; import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__BROADCAST_OF_INTENT; import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__CONTENT_PROVIDER_NOT_RESPONDING; import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__EXECUTING_SERVICE; -import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__FGS_TIMEOUT; import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT; import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__INPUT_DISPATCHING_TIMEOUT_NO_FOCUSED_WINDOW; import static com.android.internal.util.FrameworkStatsLog.ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE; @@ -549,8 +548,6 @@ public class AnrLatencyTracker implements AutoCloseable { return ANRLATENCY_REPORTED__ANR_TYPE__SHORT_FGS_TIMEOUT; case TimeoutKind.JOB_SERVICE: return ANRLATENCY_REPORTED__ANR_TYPE__JOB_SERVICE; - case TimeoutKind.FGS_TIMEOUT: - return ANRLATENCY_REPORTED__ANR_TYPE__FGS_TIMEOUT; default: return ANRLATENCY_REPORTED__ANR_TYPE__UNKNOWN_ANR_TYPE; } diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java index 2f09a5550fd4..66b2a9c8a424 100644 --- a/core/java/com/android/internal/policy/TransitionAnimation.java +++ b/core/java/com/android/internal/policy/TransitionAnimation.java @@ -1299,6 +1299,21 @@ public class TransitionAnimation { == HardwareBuffer.USAGE_PROTECTED_CONTENT; } + /** + * Returns the luminance in 0~1. The surface control is the source of the hardware buffer, + * which will be used if the buffer is protected from reading. + */ + public static float getBorderLuma(@NonNull HardwareBuffer hwBuffer, + @NonNull ColorSpace colorSpace, @NonNull SurfaceControl sourceSurfaceControl) { + if (hasProtectedContent(hwBuffer)) { + // The buffer cannot be read. Capture another buffer which excludes protected content + // from the source surface. + return getBorderLuma(sourceSurfaceControl, hwBuffer.getWidth(), hwBuffer.getHeight()); + } + // Use the existing buffer directly. + return getBorderLuma(hwBuffer, colorSpace); + } + /** Returns the luminance in 0~1. */ public static float getBorderLuma(SurfaceControl surfaceControl, int w, int h) { final ScreenCapture.ScreenshotHardwareBuffer buffer = diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java index 37b72880dd0c..42fa6ac0407d 100644 --- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java +++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java @@ -135,7 +135,7 @@ public class PerfettoProtoLogImpl implements IProtoLog { 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 0104d1f56f83..000000000000 --- 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 000000000000..80d409500ef0 --- /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 0f4615a12ea2..58bddaecd3e7 100644 --- a/core/java/com/android/internal/widget/NotificationRowIconView.java +++ b/core/java/com/android/internal/widget/NotificationRowIconView.java @@ -59,7 +59,7 @@ public class NotificationRowIconView extends CachingIconView { @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 d48cdc4645c6..eaff7608ce3b 100644 --- a/core/jni/android_media_AudioSystem.cpp +++ b/core/jni/android_media_AudioSystem.cpp @@ -713,6 +713,19 @@ android_media_AudioSystem_getForceUse(JNIEnv *env, jobject thiz, jint usage) 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 @@ static const JNINativeMethod gMethods[] = 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 f8c63c80f769..f55338057718 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 3006e204a9db..2068bd7bc8ea 100644 --- a/core/jni/android_util_Binder.cpp +++ b/core/jni/android_util_Binder.cpp @@ -1411,10 +1411,8 @@ static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj, 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 31f4e641b69e..d426f1240a7f 100644 --- a/core/jni/com_android_internal_content_FileSystemUtils.cpp +++ b/core/jni/com_android_internal_content_FileSystemUtils.cpp @@ -88,7 +88,7 @@ bool punchHoles(const char *filePath, const uint64_t offset, 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 @@ bool punchHoles(const char *filePath, const uint64_t offset, 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 @@ bool punchHolesInZip(const char *filePath, uint64_t offset, uint16_t extraFieldL 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 @@ bool punchHolesInZip(const char *filePath, uint64_t offset, uint16_t extraFieldL 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 3abc462671a2..e560a944b94b 100644 --- a/core/proto/android/app/appexitinfo.proto +++ b/core/proto/android/app/appexitinfo.proto @@ -20,7 +20,7 @@ option java_multiple_files = true; 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 d9ed911515ba..c13753343ba8 100644 --- a/core/proto/android/app/appstartinfo.proto +++ b/core/proto/android/app/appstartinfo.proto @@ -20,7 +20,7 @@ option java_multiple_files = true; 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 4c84944a7382..97f81484b84d 100644 --- a/core/proto/android/os/batterystats.proto +++ b/core/proto/android/os/batterystats.proto @@ -21,7 +21,7 @@ package android.os; 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 e3a438da5abc..921c41c8e52b 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/intentresolver.proto"; 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 00127c134ce6..a1e3dc1b9b4e 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/appstatetracker.proto"; 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 2f865afd28c7..593bbc6f5d0d 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/os/worksource.proto"; 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/coretests/OWNERS b/core/tests/coretests/OWNERS index b7e008b196ff..b669e3bc4f30 100644 --- a/core/tests/coretests/OWNERS +++ b/core/tests/coretests/OWNERS @@ -3,3 +3,4 @@ include platform/frameworks/base:/services/core/java/com/android/server/am/OWNER per-file BinderTest.java = file:platform/frameworks/native:/libs/binder/OWNERS per-file ParcelTest.java = file:platform/frameworks/native:/libs/binder/OWNERS per-file SurfaceControlRegistryTests.java = file:/services/core/java/com/android/server/wm/OWNERS +per-file VintfObjectTest.java = file:platform/system/libvintf:/OWNERS diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java index 89c2b3cecfef..b972882e68e6 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 @@ public class ActivityManagerTest extends AndroidTestCase { 0x222222, // colorBackground 0x333333, // statusBarColor 0x444444, // navigationBarColor - 0, // statusBarAppearance + 0x555555, // systemBarsAppeareance + 0x666666, // topOpaqueSystemBarsAppeareance true, // ensureStatusBarContrastWhenTransparent true, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_RESIZEABLE, // resizeMode @@ -153,7 +154,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x222222, // colorBackground 0x333333, // statusBarColor 0x444444, // navigationBarColor - 0, // statusBarAppearance + 0x555555, // systemBarsAppeareance + 0x666666, // topOpaqueSystemBarsAppeareance false, // ensureStatusBarContrastWhenTransparent false, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_UNRESIZEABLE, // resizeMode @@ -169,7 +171,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x2222222, // colorBackground 0x3333332, // statusBarColor 0x4444442, // navigationBarColor - 0, // statusBarAppearance + 0x5555552, // systemBarsAppeareance + 0x6666662, // topOpaqueSystemBarsAppeareance true, // ensureStatusBarContrastWhenTransparent true, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_RESIZEABLE, // resizeMode @@ -200,7 +203,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x222222, // colorBackground 0x333333, // statusBarColor 0x444444, // navigationBarColor - 0, // statusBarAppearance + 0x555555, // systemBarsAppeareance + 0x666666, // topOpaqueSystemBarsAppeareance false, // ensureStatusBarContrastWhenTransparent false, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_UNRESIZEABLE, // resizeMode @@ -223,7 +227,8 @@ public class ActivityManagerTest extends AndroidTestCase { 0x222222, // colorBackground 0x333333, // statusBarColor 0x444444, // navigationBarColor - 0, // statusBarAppearance + 0x555555, // systemBarsAppeareance + 0x666666, // topOpaqueSystemBarsAppeareance false, // ensureStatusBarContrastWhenTransparent false, // ensureNavigationBarContrastWhenTransparent RESIZE_MODE_UNRESIZEABLE, // resizeMode @@ -256,6 +261,8 @@ public class ActivityManagerTest extends AndroidTestCase { 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 3735274c1a6c..c7060adc1ca1 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.junit.Assert.assertTrue; 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.InsetsSourceControl; 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 @@ public class ClientTransactionItemTest { @Mock private IBinder mWindowClientToken; @Mock - private WindowContext mWindowContext; - @Mock private IWindow mWindow; // Can't mock final class. @@ -176,4 +175,17 @@ public class ClientTransactionItemTest { 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 1c1236279b61..98f8b7fc897c 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.annotations.RequiresFlagsEnabled; 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 @@ public class LayoutTest { 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 000000000000..3d8d8f9c126d --- /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 025e8314f5ed..da29828383b6 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.doVerifyRead; 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.ByteArrayOutputStream; 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 @@ public class BinaryXmlTest { } } } + + @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 07446e7617aa..abe9c8e337bb 100644 --- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java +++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java @@ -164,7 +164,7 @@ public class ViewFrameRateTest { mActivityRule.runOnUiThread(() -> { mMovingView.setFrameContentVelocity(1f); mMovingView.invalidate(); - runAfterDraw(() -> assertEquals(60f, mViewRoot.getLastPreferredFrameRate(), 0f)); + runAfterDraw(() -> assertEquals(80f, mViewRoot.getLastPreferredFrameRate(), 0f)); }); waitForAfterDraw(); } @@ -190,7 +190,7 @@ public class ViewFrameRateTest { 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 @@ public class ViewFrameRateTest { 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 a7f817665f23..94e187aed698 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -1287,6 +1287,31 @@ public class ViewRootImplTest { } @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 b5c264c4ae5e..5a6824bf0d7e 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 class AccessibilityServiceConnectionImpl extends IAccessibilityServiceCon 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 bcdac610a49d..000000000000 --- 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 000000000000..1f2e24aa8c68 --- /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 d359a9050a0f..3cff91597939 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -1149,6 +1149,8 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu /** * 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/ktfmt_includes.txt b/ktfmt_includes.txt deleted file mode 100644 index 0ac6265ecf27..000000000000 --- a/ktfmt_includes.txt +++ /dev/null @@ -1,740 +0,0 @@ -+services/permission -+packages/SystemUI --packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt --packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt --packages/SystemUI/checks/src/com/android/internal/systemui/lint/BroadcastSentViaContextDetector.kt --packages/SystemUI/checks/src/com/android/internal/systemui/lint/RegisterReceiverViaContextDetector.kt --packages/SystemUI/checks/src/com/android/internal/systemui/lint/SoftwareBitmapDetector.kt --packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt --packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/View.kt --packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt --packages/SystemUI/shared/src/com/android/systemui/flags/FlagListenable.kt --packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt --packages/SystemUI/shared/src/com/android/systemui/flags/FlagSerializer.kt --packages/SystemUI/shared/src/com/android/systemui/flags/FlagSettingsHelper.kt --packages/SystemUI/shared/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimator.kt --packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt --packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt --packages/SystemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt --packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt --packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt --packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt --packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt --packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt --packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt --packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt --packages/SystemUI/src/com/android/keyguard/BouncerPanelExpansionCalculator.kt --packages/SystemUI/src/com/android/keyguard/ClockEventController.kt --packages/SystemUI/src/com/android/keyguard/KeyguardBiometricLockoutLogger.kt --packages/SystemUI/src/com/android/keyguard/KeyguardUnfoldTransition.kt --packages/SystemUI/src/com/android/keyguard/KeyguardUserSwitcherAnchor.kt --packages/SystemUI/src/com/android/keyguard/clock/ClockPalette.kt --packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt --packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt --packages/SystemUI/src/com/android/systemui/BootCompleteCache.kt --packages/SystemUI/src/com/android/systemui/BootCompleteCacheImpl.kt --packages/SystemUI/src/com/android/systemui/CameraAvailabilityListener.kt --packages/SystemUI/src/com/android/systemui/ChooserSelector.kt --packages/SystemUI/src/com/android/systemui/DarkReceiverImpl.kt --packages/SystemUI/src/com/android/systemui/DisplayCutoutBaseView.kt --packages/SystemUI/src/com/android/systemui/DualToneHandler.kt --packages/SystemUI/src/com/android/systemui/FaceScanningOverlay.kt --packages/SystemUI/src/com/android/systemui/ScreenDecorHwcLayer.kt --packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt --packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt --packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt --packages/SystemUI/src/com/android/systemui/assist/AssistLogger.kt --packages/SystemUI/src/com/android/systemui/assist/AssistantInvocationEvent.kt --packages/SystemUI/src/com/android/systemui/assist/AssistantSessionEvent.kt --packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt --packages/SystemUI/src/com/android/systemui/biometrics/AlternateUdfpsTouchProvider.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt --packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt --packages/SystemUI/src/com/android/systemui/biometrics/BiometricDisplayListener.kt --packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDrawable.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpDrawable.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHapticsSimulator.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt --packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt --packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt --packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt --packages/SystemUI/src/com/android/systemui/broadcast/ActionReceiver.kt --packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt --packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcherStartable.kt --packages/SystemUI/src/com/android/systemui/broadcast/BroadcastSender.kt --packages/SystemUI/src/com/android/systemui/broadcast/PendingRemovalStore.kt --packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt --packages/SystemUI/src/com/android/systemui/broadcast/logging/BroadcastDispatcherLogger.kt --packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt --packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt --packages/SystemUI/src/com/android/systemui/controls/ControlStatus.kt --packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLogger.kt --packages/SystemUI/src/com/android/systemui/controls/ControlsMetricsLoggerImpl.kt --packages/SystemUI/src/com/android/systemui/controls/ControlsServiceInfo.kt --packages/SystemUI/src/com/android/systemui/controls/CustomIconCache.kt --packages/SystemUI/src/com/android/systemui/controls/TooltipManager.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlInfo.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingController.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapper.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManager.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfiguration.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImpl.kt --packages/SystemUI/src/com/android/systemui/controls/controller/ServiceWrapper.kt --packages/SystemUI/src/com/android/systemui/controls/controller/StatefulControlSubscriber.kt --packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsComponent.kt --packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsFeatureEnabled.kt --packages/SystemUI/src/com/android/systemui/controls/dagger/ControlsModule.kt --packages/SystemUI/src/com/android/systemui/controls/management/AllModel.kt --packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsAnimations.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingController.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsModel.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestDialog.kt --packages/SystemUI/src/com/android/systemui/controls/management/ControlsRequestReceiver.kt --packages/SystemUI/src/com/android/systemui/controls/management/FavoritesModel.kt --packages/SystemUI/src/com/android/systemui/controls/management/ManagementPageIndicator.kt --packages/SystemUI/src/com/android/systemui/controls/management/StructureAdapter.kt --packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt --packages/SystemUI/src/com/android/systemui/controls/ui/Behavior.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ChallengeDialogs.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinator.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlViewHolder.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlWithState.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlsActivity.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiController.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt --packages/SystemUI/src/com/android/systemui/controls/ui/CornerDrawable.kt --packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt --packages/SystemUI/src/com/android/systemui/controls/ui/RenderInfo.kt --packages/SystemUI/src/com/android/systemui/controls/ui/StatusBehavior.kt --packages/SystemUI/src/com/android/systemui/controls/ui/TemperatureControlBehavior.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ThumbnailBehavior.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ToggleBehavior.kt --packages/SystemUI/src/com/android/systemui/controls/ui/ToggleRangeBehavior.kt --packages/SystemUI/src/com/android/systemui/controls/ui/TouchBehavior.kt --packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt --packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderFactory.kt --packages/SystemUI/src/com/android/systemui/decor/CutoutDecorProviderImpl.kt --packages/SystemUI/src/com/android/systemui/decor/DecorProvider.kt --packages/SystemUI/src/com/android/systemui/decor/DecorProviderFactory.kt --packages/SystemUI/src/com/android/systemui/decor/FaceScanningProviderFactory.kt --packages/SystemUI/src/com/android/systemui/decor/OverlayWindow.kt --packages/SystemUI/src/com/android/systemui/decor/PrivacyDotDecorProviderFactory.kt --packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderFactory.kt --packages/SystemUI/src/com/android/systemui/decor/RoundedCornerDecorProviderImpl.kt --packages/SystemUI/src/com/android/systemui/decor/RoundedCornerResDelegate.kt --packages/SystemUI/src/com/android/systemui/demomode/DemoModeAvailabilityTracker.kt --packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt --packages/SystemUI/src/com/android/systemui/doze/util/BurnInHelper.kt --packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt --packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt --packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt --packages/SystemUI/src/com/android/systemui/dump/DumpsysTableLogger.kt --packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt --packages/SystemUI/src/com/android/systemui/dump/LogBufferFreezer.kt --packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt --packages/SystemUI/src/com/android/systemui/flags/Flags.kt --packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt --packages/SystemUI/src/com/android/systemui/flags/SystemPropertiesHelper.kt --packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt --packages/SystemUI/src/com/android/systemui/keyguard/LifecycleScreenStatusProvider.kt --packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfig.kt --packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartable.kt --packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/PrimaryBouncerInteractor.kt --packages/SystemUI/src/com/android/systemui/log/LogBufferFactory.kt --packages/SystemUI/src/com/android/systemui/media/MediaProjectionCaptureTarget.kt --packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogFactory.kt --packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt --packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogReceiver.kt --packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionCli.kt --packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManager.kt --packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerFactory.kt --packages/SystemUI/src/com/android/systemui/media/muteawait/MediaMuteAwaitLogger.kt --packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesLogger.kt --packages/SystemUI/src/com/android/systemui/media/nearby/NearbyMediaDevicesManager.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelper.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/MediaTttFlags.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ChipStateReceiver.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt --packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLogger.kt --packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorResultHandler.kt --packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt --packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionTaskView.kt --packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/RecentTasksAdapter.kt --packages/SystemUI/src/com/android/systemui/mediaprojection/devicepolicy/ScreenCaptureDevicePolicyResolver.kt --packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt --packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt --packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt --packages/SystemUI/src/com/android/systemui/notetask/NoteTaskEventLogger.kt --packages/SystemUI/src/com/android/systemui/power/BatteryStateSnapshot.kt --packages/SystemUI/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitor.kt --packages/SystemUI/src/com/android/systemui/privacy/MediaProjectionPrivacyItemMonitor.kt --packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipBuilder.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyChipEvent.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyConfig.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialog.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogController.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt --packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt --packages/SystemUI/src/com/android/systemui/privacy/logging/PrivacyLogger.kt --packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt --packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt --packages/SystemUI/src/com/android/systemui/qs/HeaderPrivacyIconsController.kt --packages/SystemUI/src/com/android/systemui/qs/QSEvents.kt --packages/SystemUI/src/com/android/systemui/qs/QSExpansionPathInterpolator.kt --packages/SystemUI/src/com/android/systemui/qs/QSFragmentDisableFlagsLogger.kt --packages/SystemUI/src/com/android/systemui/qs/QSSquishinessController.kt --packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt --packages/SystemUI/src/com/android/systemui/qs/SideLabelTileLayout.kt --packages/SystemUI/src/com/android/systemui/qs/VisibilityChangedDispatcher.kt --packages/SystemUI/src/com/android/systemui/qs/carrier/CellSignalState.kt --packages/SystemUI/src/com/android/systemui/qs/customize/CustomizeTileView.kt --packages/SystemUI/src/com/android/systemui/qs/external/CustomTileStatePersister.kt --packages/SystemUI/src/com/android/systemui/qs/external/QSExternalModule.kt --packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt --packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialogEventLogger.kt --packages/SystemUI/src/com/android/systemui/qs/external/TileServiceRequestController.kt --packages/SystemUI/src/com/android/systemui/qs/tileimpl/HeightOverrideable.kt --packages/SystemUI/src/com/android/systemui/qs/tileimpl/IgnorableChildLinearLayout.kt --packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt --packages/SystemUI/src/com/android/systemui/qs/tiles/AlarmTile.kt --packages/SystemUI/src/com/android/systemui/qs/tiles/DeviceControlsTile.kt --packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt --packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt --packages/SystemUI/src/com/android/systemui/screenshot/ImageCaptureImpl.kt --packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt --packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicy.kt --packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotPolicyImpl.kt --packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt --packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseDialog.kt --packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt --packages/SystemUI/src/com/android/systemui/settings/UserContentResolverProvider.kt --packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt --packages/SystemUI/src/com/android/systemui/settings/UserFileManager.kt --packages/SystemUI/src/com/android/systemui/settings/UserTracker.kt --packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt --packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessMirrorHandler.kt --packages/SystemUI/src/com/android/systemui/settings/brightness/MirroredBrightnessController.kt --packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt --packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt --packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt --packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt --packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt --packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt --packages/SystemUI/src/com/android/systemui/shade/ShadeHeightLogger.kt --packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt --packages/SystemUI/src/com/android/systemui/shade/ShadeWindowLogger.kt --packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt --packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt --packages/SystemUI/src/com/android/systemui/smartspace/SmartspacePrecondition.kt --packages/SystemUI/src/com/android/systemui/smartspace/SmartspaceTargetFilter.kt --packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt --packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt --packages/SystemUI/src/com/android/systemui/smartspace/filters/LockscreenAndDreamTargetFilter.kt --packages/SystemUI/src/com/android/systemui/smartspace/preconditions/LockscreenPrecondition.kt --packages/SystemUI/src/com/android/systemui/statusbar/AbstractLockscreenShadeTransitionController.kt --packages/SystemUI/src/com/android/systemui/statusbar/ActionClickLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt --packages/SystemUI/src/com/android/systemui/statusbar/LockScreenShadeOverScroller.kt --packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt --packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeScrimTransitionController.kt --packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt --packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt --packages/SystemUI/src/com/android/systemui/statusbar/NotificationClickNotifier.kt --packages/SystemUI/src/com/android/systemui/statusbar/NotificationInteractionTracker.kt --packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt --packages/SystemUI/src/com/android/systemui/statusbar/PulseExpansionHandler.kt --packages/SystemUI/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScroller.kt --packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt --packages/SystemUI/src/com/android/systemui/statusbar/commandline/CommandRegistry.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/AccessPointController.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityState.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileSignalControllerFactory.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileState.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/MobileStatusTrackerFactory.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/SignalCallback.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiState.kt --packages/SystemUI/src/com/android/systemui/statusbar/connectivity/WifiStatusTrackerFactory.kt --packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt --packages/SystemUI/src/com/android/systemui/statusbar/dagger/StartCentralSurfacesModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/disableflags/DisableFlagsLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/PrivacyDotViewController.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/StatusBarEventsModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/StatusEvent.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventChipAnimationController.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/SystemEventCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationScheduler.kt --packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/gesture/GesturePointerEventDetector.kt --packages/SystemUI/src/com/android/systemui/statusbar/gesture/GenericGestureDetector.kt --packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureHandler.kt --packages/SystemUI/src/com/android/systemui/statusbar/gesture/SwipeUpGestureLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/gesture/TapGestureDetector.kt --packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/FeedbackIcon.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/LaunchAnimationParameters.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationUtils.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationWakeUpCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/ViewGroupFadeHelper.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListAttachState.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographer.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/PipelineDumper.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/SuppressedAttachState.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolver.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coalescer/GroupCoalescerLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GutsCoordinatorLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/dagger/CoordinatorsModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/BindEventManagerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/NotifStabilityManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTracker.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifEvent.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtender.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/DebugModeFilterProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/MediaContainerController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGroupController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewListener.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifGutsViewManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifRowController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifShadeEventSource.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifStackController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewBarn.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotifViewRenderer.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NotificationVisibilityProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderExtensions.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/RootNodeController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/SectionHeaderController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationSectionHeadersModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconBuilder.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerStub.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationMemoryViewWalker.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/people/NotificationPersonExtractor.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleNotificationIdentifier.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/people/ViewPipeline.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogController.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ChannelEditorListView.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/dagger/RemoteInputViewModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCallTemplateViewWrapper.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationConversationTemplateViewWrapper.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/MediaContainerView.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationPriorityBucket.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationSectionsManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt --packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/LSShadeTransitionLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/ScreenOffAnimationController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHideIconsForBouncerManager.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLocationPublisher.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarIconBlocklist.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/StatusBarSystemEventAnimator.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometer.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallController.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallFlags.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/phone/userswitcher/StatusBarUserSwitcherContainer.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryStateNotifier.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsController.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyState.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/InflatedSmartReplyViewHolder.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisabler.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputViewController.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/SmartReplyStateInflater.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateView.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/VariableDateViewController.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/WalletController.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/WalletControllerImpl.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/RemoteInput.kt --packages/SystemUI/src/com/android/systemui/statusbar/policy/dagger/SmartRepliesInflationModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowModule.kt --packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowStateController.kt --packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt --packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt --packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarRootView.kt --packages/SystemUI/src/com/android/systemui/toast/ToastDefaultAnimation.kt --packages/SystemUI/src/com/android/systemui/toast/ToastLogger.kt --packages/SystemUI/src/com/android/systemui/unfold/FoldAodAnimationController.kt --packages/SystemUI/src/com/android/systemui/unfold/FoldStateLogger.kt --packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt --packages/SystemUI/src/com/android/systemui/unfold/UnfoldLightRevealOverlayAnimation.kt --packages/SystemUI/src/com/android/systemui/unfold/UnfoldProgressProvider.kt --packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt --packages/SystemUI/src/com/android/systemui/user/UserSwitcherRootView.kt --packages/SystemUI/src/com/android/systemui/util/AsyncActivityLauncher.kt --packages/SystemUI/src/com/android/systemui/util/ColorUtil.kt --packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt --packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt --packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt --packages/SystemUI/src/com/android/systemui/util/InitializationChecker.kt --packages/SystemUI/src/com/android/systemui/util/LargeScreenUtils.kt --packages/SystemUI/src/com/android/systemui/util/ListenerSet.kt --packages/SystemUI/src/com/android/systemui/util/NeverExactlyLinearLayout.kt --packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt --packages/SystemUI/src/com/android/systemui/util/PluralMessageFormater.kt --packages/SystemUI/src/com/android/systemui/util/RingerModeTracker.kt --packages/SystemUI/src/com/android/systemui/util/RingerModeTrackerImpl.kt --packages/SystemUI/src/com/android/systemui/util/RoundedCornerProgressDrawable.kt --packages/SystemUI/src/com/android/systemui/util/SafeMarqueeTextView.kt --packages/SystemUI/src/com/android/systemui/util/SparseArrayUtils.kt --packages/SystemUI/src/com/android/systemui/util/TraceUtils.kt --packages/SystemUI/src/com/android/systemui/util/UserAwareController.kt --packages/SystemUI/src/com/android/systemui/util/WallpaperController.kt --packages/SystemUI/src/com/android/systemui/util/animation/AnimationUtil.kt --packages/SystemUI/src/com/android/systemui/util/animation/MeasurementInput.kt --packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayout.kt --packages/SystemUI/src/com/android/systemui/util/animation/TransitionLayoutController.kt --packages/SystemUI/src/com/android/systemui/util/animation/UniqueObjectHostView.kt --packages/SystemUI/src/com/android/systemui/util/concurrency/Execution.kt --packages/SystemUI/src/com/android/systemui/util/concurrency/PendingTasksContainer.kt --packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt --packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt --packages/SystemUI/src/com/android/systemui/util/kotlin/Flow.kt --packages/SystemUI/src/com/android/systemui/util/kotlin/IpcSerializer.kt --packages/SystemUI/src/com/android/systemui/util/kotlin/nullability.kt --packages/SystemUI/src/com/android/systemui/util/recycler/HorizontalSpacerItemDecoration.kt --packages/SystemUI/src/com/android/systemui/util/view/ViewUtil.kt --packages/SystemUI/src/com/android/systemui/util/wrapper/RotationPolicyWrapper.kt --packages/SystemUI/src/com/android/systemui/volume/VolumePanelDialogReceiver.kt --packages/SystemUI/src/com/android/systemui/volume/VolumePanelFactory.kt --packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt --packages/SystemUI/tests/src/com/android/keyguard/BouncerPanelExpansionCalculatorTest.kt --packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt --packages/SystemUI/tests/src/com/android/keyguard/KeyguardBiometricLockoutLoggerTest.kt --packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt --packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt --packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.kt --packages/SystemUI/tests/src/com/android/keyguard/KeyguardUserSwitcherAnchorTest.kt --packages/SystemUI/tests/src/com/android/keyguard/clock/ClockPaletteTest.kt --packages/SystemUI/tests/src/com/android/keyguard/clock/ViewPreviewerTest.kt --packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/BootCompleteCacheTest.kt --packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt --packages/SystemUI/tests/src/com/android/systemui/DisplayCutoutBaseViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/InstanceIdSequenceFake.kt --packages/SystemUI/tests/src/com/android/systemui/ScreenDecorHwcLayerTest.kt --packages/SystemUI/tests/src/com/android/systemui/animation/DialogTransitionAnimatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/animation/GhostedViewTransitionAnimatorControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/battery/BatteryMeterViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt --packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/broadcast/ActionReceiverTest.kt --packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastDispatcherTest.kt --packages/SystemUI/tests/src/com/android/systemui/broadcast/BroadcastSenderTest.kt --packages/SystemUI/tests/src/com/android/systemui/broadcast/PendingRemovalStoreTest.kt --packages/SystemUI/tests/src/com/android/systemui/broadcast/UserBroadcastDispatcherTest.kt --packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt --packages/SystemUI/tests/src/com/android/systemui/camera/CameraIntentsTest.kt --packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/CustomIconCacheTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/AuxiliaryPersistenceWrapperTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlActionCoordinatorImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsBindingControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsFavoritePersistenceWrapperTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsProviderLifecycleManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsTileResourceConfigurationImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/ServiceWrapperTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/controller/StatefulControlSubscriberTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/dagger/ControlsComponentTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/management/AllModelTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestDialogTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsRequestReceiverTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/management/FavoritesModelTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlViewHolderTest.kt --packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt --packages/SystemUI/tests/src/com/android/systemui/decor/CutoutDecorProviderFactoryTest.kt --packages/SystemUI/tests/src/com/android/systemui/decor/OverlayWindowTest.kt --packages/SystemUI/tests/src/com/android/systemui/decor/PrivacyDotDecorProviderFactoryTest.kt --packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerDecorProviderFactoryTest.kt --packages/SystemUI/tests/src/com/android/systemui/decor/RoundedCornerResDelegateTest.kt --packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt --packages/SystemUI/tests/src/com/android/systemui/dump/DumpsysTableLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferFreezerTest.kt --packages/SystemUI/tests/src/com/android/systemui/dump/LogBufferHelper.kt --packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt --packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt --packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt --packages/SystemUI/tests/src/com/android/systemui/flags/FlagManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceConfigTest.kt --packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/MuteQuickAffordanceCoreStartableTest.kt --packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepositoryTest.kt --packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt --packages/SystemUI/tests/src/com/android/systemui/lifecycle/InstantTaskExecutorRule.kt --packages/SystemUI/tests/src/com/android/systemui/media/muteawait/MediaMuteAwaitConnectionManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/nearby/NearbyMediaDevicesManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/MediaTttCommandLineHelperTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt --packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderUiEventLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/navigationbar/TaskbarDelegateTest.kt --packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt --packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt --packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt --packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt --packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentDisableFlagsLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/SettingObserverTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/carrier/CellSignalStateTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogEventLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileViewImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt --packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/screenrecord/ScreenRecordDialogTest.kt --packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotPolicyImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/settings/UserTrackerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/settings/brightness/BrightnessSliderControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt --packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt --packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt --packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt --packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt --packages/SystemUI/tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/BlurUtilsTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/DragDownHelperTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/LSShadeTransitionLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/LightRevealScrimTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/SingleShadeLockScreenOverScrollerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateEventTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/VibratorHelperTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/commandline/CommandRegistryTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/AccessPointControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableFlagsLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/disableflags/DisableStateTrackerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/gesture/GenericGestureDetectorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifLiveDataStoreImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifPipelineChoreographerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/TargetSdkResolverTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/DataStoreCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RemoteInputCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ShadeEventCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionInconsistencyTrackerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/SelfTrackingLifetimeExtenderTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/provider/VisualStabilityProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/RenderStageManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/IconManagerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ChannelEditorDialogControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/MediaContainerViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ConfigurationControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/FoldStateListenerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxBackgroundProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationIconContainerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarMoveFromCenterAnimationControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallChronometerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ongoingcall/OngoingCallLoggerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryStateNotifierTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ClockTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardQsUserSwitchControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputQuickSettingsDisablerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SafetyControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/VariableDateViewControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/WalletControllerImplTest.kt --packages/SystemUI/tests/src/com/android/systemui/statusbar/window/StatusBarWindowStateControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldLatencyTrackerTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldTransitionWallpaperControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/util/FoldableTestUtils.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt --packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt --packages/SystemUI/tests/src/com/android/systemui/usb/UsbPermissionActivityTest.kt --packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/FakeSharedPreferencesTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/ListenerSetTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/RingerModeLiveDataTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/WallpaperControllerTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/animation/AnimationUtilTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/kotlin/FlowUtilTests.kt --packages/SystemUI/tests/src/com/android/systemui/util/kotlin/IpcSerializerTest.kt --packages/SystemUI/tests/src/com/android/systemui/util/kotlin/SuspendUtilTests.kt --packages/SystemUI/tests/src/com/android/systemui/util/view/ViewUtilTest.kt --packages/SystemUI/tests/utils/src/com/android/systemui/flags/FakeFeatureFlags.kt --packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSharedPreferences.kt --packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/SizeScreenStatusProvider.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldMain.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt --packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt 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 6b957114c00b..23dc96c39bde 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -200,6 +200,10 @@ class DividerPresenter implements View.OnTouchListener { } // At this point, a divider is required. + final TaskFragmentContainer primaryContainer = + topSplitContainer.getPrimaryContainer(); + final TaskFragmentContainer secondaryContainer = + topSplitContainer.getSecondaryContainer(); // Create the decor surface if one is not available yet. final SurfaceControl decorSurface = parentInfo.getDecorSurface(); @@ -207,42 +211,43 @@ class DividerPresenter implements View.OnTouchListener { // Clean up when the decor surface is currently unavailable. removeDivider(); // Request to create the decor surface - createOrMoveDecorSurfaceLocked(wct, topSplitContainer.getPrimaryContainer()); + createOrMoveDecorSurfaceLocked(wct, primaryContainer); return; } // Update the decor surface owner if needed. boolean isDraggableExpandType = SplitAttributesHelper.isDraggableExpandType(splitAttributes); - final TaskFragmentContainer decorSurfaceOwnerContainer = isDraggableExpandType - ? topSplitContainer.getSecondaryContainer() - : topSplitContainer.getPrimaryContainer(); + final TaskFragmentContainer decorSurfaceOwnerContainer = + isDraggableExpandType ? secondaryContainer : primaryContainer; if (!Objects.equals( mDecorSurfaceOwner, decorSurfaceOwnerContainer.getTaskFragmentToken())) { createOrMoveDecorSurfaceLocked(wct, decorSurfaceOwnerContainer); } - final boolean isVerticalSplit = isVerticalSplit(topSplitContainer); - final boolean isReversedLayout = isReversedLayout( - topSplitContainer.getCurrentSplitAttributes(), - parentInfo.getConfiguration()); + + final Configuration parentConfiguration = parentInfo.getConfiguration(); + final Rect taskBounds = parentConfiguration.windowConfiguration.getBounds(); + final boolean isVerticalSplit = isVerticalSplit(splitAttributes); + final boolean isReversedLayout = isReversedLayout(splitAttributes, parentConfiguration); + final int dividerWidthPx = getDividerWidthPx(dividerAttributes); updateProperties( new Properties( - parentInfo.getConfiguration(), + parentConfiguration, dividerAttributes, decorSurface, getInitialDividerPosition( - topSplitContainer, isVerticalSplit, isReversedLayout), + primaryContainer, secondaryContainer, taskBounds, + dividerWidthPx, isDraggableExpandType, isVerticalSplit, + isReversedLayout), isVerticalSplit, isReversedLayout, parentInfo.getDisplayId(), isDraggableExpandType, - getContainerBackgroundColor(topSplitContainer.getPrimaryContainer(), - DEFAULT_PRIMARY_VEIL_COLOR), - getContainerBackgroundColor(topSplitContainer.getSecondaryContainer(), - DEFAULT_SECONDARY_VEIL_COLOR) - )); + primaryContainer, + secondaryContainer) + ); } } @@ -338,32 +343,31 @@ class DividerPresenter implements View.OnTouchListener { @VisibleForTesting static int getInitialDividerPosition( - @NonNull SplitContainer splitContainer, + @NonNull TaskFragmentContainer primaryContainer, + @NonNull TaskFragmentContainer secondaryContainer, + @NonNull Rect taskBounds, + int dividerWidthPx, + boolean isDraggableExpandType, boolean isVerticalSplit, boolean isReversedLayout) { - final Rect primaryBounds = - splitContainer.getPrimaryContainer().getLastRequestedBounds(); - final Rect secondaryBounds = - splitContainer.getSecondaryContainer().getLastRequestedBounds(); - final SplitAttributes splitAttributes = splitContainer.getCurrentSplitAttributes(); - - if (SplitAttributesHelper.isDraggableExpandType(splitAttributes)) { - // If the container is fully expanded by dragging the divider, we display the divider - // on the edge. - final int dividerWidth = getDividerWidthPx(splitAttributes.getDividerAttributes()); + if (isDraggableExpandType) { + // If the secondary container is fully expanded by dragging the divider, we display the + // divider on the edge. final int fullyExpandedPosition = isVerticalSplit - ? primaryBounds.right - dividerWidth - : primaryBounds.bottom - dividerWidth; + ? taskBounds.width() - dividerWidthPx + : taskBounds.height() - dividerWidthPx; return isReversedLayout ? fullyExpandedPosition : 0; } else { + final Rect primaryBounds = primaryContainer.getLastRequestedBounds(); + final Rect secondaryBounds = secondaryContainer.getLastRequestedBounds(); return isVerticalSplit ? Math.min(primaryBounds.right, secondaryBounds.right) : Math.min(primaryBounds.bottom, secondaryBounds.bottom); } } - private static boolean isVerticalSplit(@NonNull SplitContainer splitContainer) { - final int layoutDirection = splitContainer.getCurrentSplitAttributes().getLayoutDirection(); + private static boolean isVerticalSplit(@NonNull SplitAttributes splitAttributes) { + final int layoutDirection = splitAttributes.getLayoutDirection(); switch (layoutDirection) { case SplitAttributes.LayoutDirection.LEFT_TO_RIGHT: case SplitAttributes.LayoutDirection.RIGHT_TO_LEFT: @@ -510,7 +514,7 @@ class DividerPresenter implements View.OnTouchListener { if (mProperties != null && mRenderer != null) { final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds(); mDividerPosition = calculateDividerPosition( - event, taskBounds, mRenderer.mDividerWidthPx, + event, taskBounds, mProperties.mDividerWidthPx, mProperties.mDividerAttributes, mProperties.mIsVerticalSplit, calculateMinPosition(), calculateMaxPosition()); mRenderer.setDividerPosition(mDividerPosition); @@ -676,8 +680,8 @@ class DividerPresenter implements View.OnTouchListener { final int minPosition = calculateMinPosition(); final int maxPosition = calculateMaxPosition(); final int fullyExpandedPosition = mProperties.mIsVerticalSplit - ? taskBounds.right - mRenderer.mDividerWidthPx - : taskBounds.bottom - mRenderer.mDividerWidthPx; + ? taskBounds.width() - mProperties.mDividerWidthPx + : taskBounds.height() - mProperties.mDividerWidthPx; if (isDraggingToFullscreenAllowed(mProperties.mDividerAttributes)) { final float displayDensity = getDisplayDensity(); @@ -782,7 +786,7 @@ class DividerPresenter implements View.OnTouchListener { private int calculateMinPosition() { return calculateMinPosition( mProperties.mConfiguration.windowConfiguration.getBounds(), - mRenderer.mDividerWidthPx, mProperties.mDividerAttributes, + mProperties.mDividerWidthPx, mProperties.mDividerAttributes, mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout); } @@ -790,7 +794,7 @@ class DividerPresenter implements View.OnTouchListener { private int calculateMaxPosition() { return calculateMaxPosition( mProperties.mConfiguration.windowConfiguration.getBounds(), - mRenderer.mDividerWidthPx, mProperties.mDividerAttributes, + mProperties.mDividerWidthPx, mProperties.mDividerAttributes, mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout); } @@ -828,13 +832,12 @@ class DividerPresenter implements View.OnTouchListener { * Returns the new split ratio of the {@link SplitContainer} based on the current divider * position. */ - float calculateNewSplitRatio(@NonNull SplitContainer topSplitContainer) { + float calculateNewSplitRatio() { synchronized (mLock) { return calculateNewSplitRatio( - topSplitContainer, mDividerPosition, mProperties.mConfiguration.windowConfiguration.getBounds(), - mRenderer.mDividerWidthPx, + mProperties.mDividerWidthPx, mProperties.mIsVerticalSplit, mProperties.mIsReversedLayout, calculateMinPosition(), @@ -846,21 +849,20 @@ class DividerPresenter implements View.OnTouchListener { private static boolean isDraggingToFullscreenAllowed( @NonNull DividerAttributes dividerAttributes) { // TODO(b/293654166) Use DividerAttributes.isDraggingToFullscreenAllowed when extension is - // updated. - return true; + // updated to v7. + return false; } /** * Returns the new split ratio of the {@link SplitContainer} based on the current divider * position. * - * @param topSplitContainer the {@link SplitContainer} for which to compute the split ratio. * @param dividerPosition the divider position. See {@link #mDividerPosition}. * @param taskBounds the task bounds * @param dividerWidthPx the width of the divider in pixels. * @param isVerticalSplit if {@code true}, the split is a vertical split. If {@code false}, the * split is a horizontal split. See - * {@link #isVerticalSplit(SplitContainer)}. + * {@link #isVerticalSplit(SplitAttributes)}. * @param isReversedLayout if {@code true}, the split layout is reversed, i.e. right-to-left or * bottom-to-top. If {@code false}, the split is not reversed, i.e. * left-to-right or top-to-bottom. See @@ -871,7 +873,6 @@ class DividerPresenter implements View.OnTouchListener { */ @VisibleForTesting static float calculateNewSplitRatio( - @NonNull SplitContainer topSplitContainer, int dividerPosition, @NonNull Rect taskBounds, int dividerWidthPx, @@ -896,8 +897,6 @@ class DividerPresenter implements View.OnTouchListener { dividerPosition = Math.clamp(dividerPosition, minPosition, maxPosition); } - final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer(); - final Rect origPrimaryBounds = primaryContainer.getLastRequestedBounds(); final int usableSize = isVerticalSplit ? taskBounds.width() - dividerWidthPx : taskBounds.height() - dividerWidthPx; @@ -905,13 +904,13 @@ class DividerPresenter implements View.OnTouchListener { final float newRatio; if (isVerticalSplit) { final int newPrimaryWidth = isReversedLayout - ? (origPrimaryBounds.right - (dividerPosition + dividerWidthPx)) - : (dividerPosition - origPrimaryBounds.left); + ? taskBounds.width() - (dividerPosition + dividerWidthPx) + : dividerPosition; newRatio = 1.0f * newPrimaryWidth / usableSize; } else { final int newPrimaryHeight = isReversedLayout - ? (origPrimaryBounds.bottom - (dividerPosition + dividerWidthPx)) - : (dividerPosition - origPrimaryBounds.top); + ? taskBounds.height() - (dividerPosition + dividerWidthPx) + : dividerPosition; newRatio = 1.0f * newPrimaryHeight / usableSize; } return newRatio; @@ -964,8 +963,11 @@ class DividerPresenter implements View.OnTouchListener { 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 Properties( @@ -977,8 +979,8 @@ class DividerPresenter implements View.OnTouchListener { boolean isReversedLayout, int displayId, boolean isDraggableExpandType, - @NonNull Color primaryVeilColor, - @NonNull Color secondaryVeilColor) { + @NonNull TaskFragmentContainer primaryContainer, + @NonNull TaskFragmentContainer secondaryContainer) { mConfiguration = configuration; mDividerAttributes = dividerAttributes; mDecorSurface = decorSurface; @@ -987,8 +989,9 @@ class DividerPresenter implements View.OnTouchListener { mIsReversedLayout = isReversedLayout; mDisplayId = displayId; mIsDraggableExpandType = isDraggableExpandType; - mPrimaryVeilColor = primaryVeilColor; - mSecondaryVeilColor = secondaryVeilColor; + mPrimaryContainer = primaryContainer; + mSecondaryContainer = secondaryContainer; + mDividerWidthPx = getDividerWidthPx(dividerAttributes); } /** @@ -1011,8 +1014,8 @@ class DividerPresenter implements View.OnTouchListener { && 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( @@ -1055,7 +1058,6 @@ class DividerPresenter implements View.OnTouchListener { private final View.OnTouchListener mListener; @NonNull private Properties mProperties; - private int mDividerWidthPx; private int mHandleWidthPx; @Nullable private SurfaceControl mPrimaryVeil; @@ -1095,7 +1097,6 @@ class DividerPresenter implements View.OnTouchListener { /** Updates the divider when initializing or when properties are changed */ @VisibleForTesting void update() { - mDividerWidthPx = getDividerWidthPx(mProperties.mDividerAttributes); mDividerPosition = mProperties.mInitialDividerPosition; mWindowlessWindowManager.setConfiguration(mProperties.mConfiguration); @@ -1161,15 +1162,17 @@ class DividerPresenter implements View.OnTouchListener { // When the divider drag handle width is larger than the divider width, the position // of the divider surface is adjusted so that it is large enough to host both the // divider line and the divider drag handle. - mDividerSurfaceWidthPx = Math.max(mDividerWidthPx, mHandleWidthPx); + mDividerSurfaceWidthPx = Math.max(mProperties.mDividerWidthPx, mHandleWidthPx); + dividerSurfacePosition = mProperties.mIsReversedLayout + ? mDividerPosition + : mDividerPosition + mProperties.mDividerWidthPx - mDividerSurfaceWidthPx; dividerSurfacePosition = - mProperties.mIsReversedLayout - ? mDividerPosition - : mDividerPosition + mDividerWidthPx - mDividerSurfaceWidthPx; - dividerSurfacePosition = Math.clamp(dividerSurfacePosition, 0, - mProperties.mIsVerticalSplit ? taskBounds.width() : taskBounds.height()); + Math.clamp(dividerSurfacePosition, 0, + mProperties.mIsVerticalSplit + ? taskBounds.width() - mDividerSurfaceWidthPx + : taskBounds.height() - mDividerSurfaceWidthPx); } else { - mDividerSurfaceWidthPx = mDividerWidthPx; + mDividerSurfaceWidthPx = mProperties.mDividerWidthPx; dividerSurfacePosition = mDividerPosition; } @@ -1182,16 +1185,9 @@ class DividerPresenter implements View.OnTouchListener { } // Update divider line position in the surface - if (!mProperties.mIsReversedLayout) { - final int offset = mDividerPosition - dividerSurfacePosition; - mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0); - mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset); - } else { - // For reversed layout, the divider line is always at the start of the divider - // surface. - mDividerLine.setX(0); - mDividerLine.setY(0); - } + final int offset = mDividerPosition - dividerSurfacePosition; + mDividerLine.setX(mProperties.mIsVerticalSplit ? offset : 0); + mDividerLine.setY(mProperties.mIsVerticalSplit ? 0 : offset); if (mIsDragging) { updateVeils(t); @@ -1241,8 +1237,10 @@ class DividerPresenter implements View.OnTouchListener { final Rect taskBounds = mProperties.mConfiguration.windowConfiguration.getBounds(); mDividerLine.setLayoutParams( mProperties.mIsVerticalSplit - ? new FrameLayout.LayoutParams(mDividerWidthPx, taskBounds.height()) - : new FrameLayout.LayoutParams(taskBounds.width(), mDividerWidthPx) + ? new FrameLayout.LayoutParams( + mProperties.mDividerWidthPx, taskBounds.height()) + : new FrameLayout.LayoutParams( + taskBounds.width(), mProperties.mDividerWidthPx) ); if (mProperties.mDividerAttributes.getDividerType() == DividerAttributes.DIVIDER_TYPE_DRAGGABLE) { @@ -1330,8 +1328,12 @@ class DividerPresenter implements View.OnTouchListener { } 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) @@ -1352,13 +1354,14 @@ class DividerPresenter implements View.OnTouchListener { Rect secondaryBounds; if (mProperties.mIsVerticalSplit) { final Rect boundsLeft = new Rect(0, 0, mDividerPosition, taskBounds.height()); - final Rect boundsRight = new Rect(mDividerPosition + mDividerWidthPx, 0, + final Rect boundsRight = new Rect(mDividerPosition + mProperties.mDividerWidthPx, 0, taskBounds.width(), taskBounds.height()); primaryBounds = mProperties.mIsReversedLayout ? boundsRight : boundsLeft; secondaryBounds = mProperties.mIsReversedLayout ? boundsLeft : boundsRight; } else { final Rect boundsTop = new Rect(0, 0, taskBounds.width(), mDividerPosition); - final Rect boundsBottom = new Rect(0, mDividerPosition + mDividerWidthPx, + final Rect boundsBottom = new Rect( + 0, mDividerPosition + mProperties.mDividerWidthPx, taskBounds.width(), taskBounds.height()); primaryBounds = mProperties.mIsReversedLayout ? boundsBottom : boundsTop; secondaryBounds = mProperties.mIsReversedLayout ? boundsTop : boundsBottom; diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java index c708da97d908..ee00c4cd67eb 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java @@ -510,7 +510,7 @@ class TaskContainer { return; } final TaskFragmentContainer primaryContainer = topSplitContainer.getPrimaryContainer(); - final float newRatio = dividerPresenter.calculateNewSplitRatio(topSplitContainer); + final float newRatio = dividerPresenter.calculateNewSplitRatio(); // If the primary container is fully expanded, we should finish all the associated // secondary containers. 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 20626c79714e..3f676079f080 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 @@ -144,6 +144,7 @@ public class DividerPresenterTest { new SplitAttributes.Builder() .setDividerAttributes(DEFAULT_DIVIDER_ATTRIBUTES) .build()); + final Rect mockTaskBounds = new Rect(0, 0, 2000, 1000); final TaskFragmentContainer mockPrimaryContainer = createMockTaskFragmentContainer( mPrimaryContainerToken, new Rect(0, 0, 950, 1000)); @@ -158,13 +159,15 @@ public class DividerPresenterTest { DEFAULT_DIVIDER_ATTRIBUTES, mSurfaceControl, getInitialDividerPosition( - mSplitContainer, true /* isVerticalSplit */, false /* isReversedLayout */), + mockPrimaryContainer, mockSecondaryContainer, mockTaskBounds, + 50 /* divideWidthPx */, false /* isDraggableExpandType */, + true /* isVerticalSplit */, false /* isReversedLayout */), true /* isVerticalSplit */, false /* isReversedLayout */, Display.DEFAULT_DISPLAY, false /* isDraggableExpandType */, - Color.valueOf(Color.BLACK), /* primaryVeilColor */ - Color.valueOf(Color.GRAY) /* secondaryVeilColor */ + mockPrimaryContainer, + mockSecondaryContainer ); mDividerPresenter = new DividerPresenter( @@ -502,7 +505,6 @@ public class DividerPresenterTest { assertEquals( 0.3f, // Primary is 300px after dragging. DividerPresenter.calculateNewSplitRatio( - mSplitContainer, dividerPosition, taskBounds, dividerWidthPx, @@ -518,7 +520,6 @@ public class DividerPresenterTest { assertEquals( DividerPresenter.RATIO_EXPANDED_SECONDARY, DividerPresenter.calculateNewSplitRatio( - mSplitContainer, dividerPosition, taskBounds, dividerWidthPx, @@ -535,7 +536,6 @@ public class DividerPresenterTest { assertEquals( 0.2f, // Adjusted to the minPosition 200 DividerPresenter.calculateNewSplitRatio( - mSplitContainer, dividerPosition, taskBounds, dividerWidthPx, @@ -569,7 +569,6 @@ public class DividerPresenterTest { // After dragging, secondary is [0, 0, 2000, 300]. Primary is [0, 400, 2000, 1100]. 0.7f, DividerPresenter.calculateNewSplitRatio( - mSplitContainer, dividerPosition, taskBounds, dividerWidthPx, @@ -587,7 +586,6 @@ public class DividerPresenterTest { // The primary (bottom) container is expanded DividerPresenter.RATIO_EXPANDED_PRIMARY, DividerPresenter.calculateNewSplitRatio( - mSplitContainer, dividerPosition, taskBounds, dividerWidthPx, @@ -605,7 +603,6 @@ public class DividerPresenterTest { // Adjusted to minPosition 200, so the primary (bottom) container is 800. 0.8f, DividerPresenter.calculateNewSplitRatio( - mSplitContainer, dividerPosition, taskBounds, dividerWidthPx, diff --git a/libs/WindowManager/Shell/aconfig/multitasking.aconfig b/libs/WindowManager/Shell/aconfig/multitasking.aconfig index 08e695b6d6d2..15f8c328bb56 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 @@ flag { 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 12d19279111a..ace2c131050c 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.core.animation.AnimatorTestRule 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 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 @@ class BubbleExpandedViewPinControllerTest { 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 @@ class BubbleExpandedViewPinControllerTest { @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 @@ class BubbleExpandedViewPinControllerTest { 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) + assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnLeft()) + assertThat(testListener.locationChanges).containsExactly(LEFT) + assertThat(testListener.locationReleases).isEmpty() + } - val expectedDropTargetBounds = getExpectedDropTargetBounds(onLeft = true) - assertThat(dropTargetView!!.layoutParams.width).isEqualTo(expectedDropTargetBounds.width()) - assertThat(dropTargetView!!.layoutParams.height) - .isEqualTo(expectedDropTargetBounds.height()) + /** Drag crosses to the other side. Show drop target and trigger a location change. */ + @Test + fun drag_leftToRight() { + getInstrumentation().runOnMainSync { + controller.onDragStart(initialLocationOnLeft = true) + controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) + } + waitForAnimateIn() - assertThat(testListener.locationChanges).containsExactly(BubbleBarLocation.LEFT) + assertThat(dropTargetView).isNotNull() + assertThat(dropTargetView!!.alpha).isEqualTo(1f) + assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight()) + assertThat(testListener.locationChanges).containsExactly(RIGHT) assertThat(testListener.locationReleases).isEmpty() - - // Finish the drag - runOnMainSync { controller.onDragEnd() } - assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.LEFT) } + /** + * 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_toLeftAndBackToRight() { - // Drag to left - runOnMainSync { + fun drag_rightToLeftToRight() { + getInstrumentation().runOnMainSync { controller.onDragStart(initialLocationOnLeft = false) - controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + 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) + assertThat(dropTargetView!!.bounds()).isEqualTo(getExpectedDropTargetBoundsOnRight()) + assertThat(testListener.locationChanges).containsExactly(LEFT, RIGHT).inOrder() + assertThat(testListener.locationReleases).isEmpty() + } - val expectedDropTargetBounds = getExpectedDropTargetBounds(onLeft = false) - assertThat(dropTargetView!!.layoutParams.width).isEqualTo(expectedDropTargetBounds.width()) - assertThat(dropTargetView!!.layoutParams.height) - .isEqualTo(expectedDropTargetBounds.height()) + /** + * 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_leftToRightToLeft() { + getInstrumentation().runOnMainSync { + controller.onDragStart(initialLocationOnLeft = true) + controller.onDragUpdate(pointOnLeft.x, pointOnLeft.y) + } + waitForAnimateIn() + assertThat(dropTargetView).isNull() - assertThat(testListener.locationChanges) - .containsExactly(BubbleBarLocation.LEFT, BubbleBarLocation.RIGHT) - assertThat(testListener.locationReleases).isEmpty() + getInstrumentation().runOnMainSync { + controller.onDragUpdate(pointOnRight.x, pointOnRight.y) + } + waitForAnimateIn() + assertThat(dropTargetView).isNotNull() - // Release the view - runOnMainSync { controller.onDragEnd() } - assertThat(testListener.locationReleases).containsExactly(BubbleBarLocation.RIGHT) + 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_toLeftInExclusionRect() { - runOnMainSync { + 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 @@ class BubbleExpandedViewPinControllerTest { 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 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 toggleSetDropTargetHidden_dropTargetExists() { - runOnMainSync { + 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()) - runOnMainSync { controller.setDropTargetHidden(true) } - waitForAnimateOut() - assertThat(dropTargetView).isNotNull() - assertThat(dropTargetView!!.alpha).isEqualTo(0f) + assertThat(testListener.locationChanges).containsExactly(LEFT) + assertThat(testListener.locationReleases).isEmpty() + } - runOnMainSync { controller.setDropTargetHidden(false) } + /** 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 toggleSetDropTargetHidden_noDropTarget() { - runOnMainSync { controller.setDropTargetHidden(true) } + 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) + + getInstrumentation().runOnMainSync { controller.onStuckToDismissTarget() } waitForAnimateOut() - assertThat(dropTargetView).isNull() + 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).isNull() + assertThat(dropTargetView).isNotNull() + assertThat(dropTargetView!!.alpha).isEqualTo(1f) + getInstrumentation().runOnMainSync { controller.onStuckToDismissTarget() } + waitForAnimateOut() + 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 6ca6517abbb0..dc022b4afd3b 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 @@ public class TransitionUtil { /** 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 @@ public class TransitionUtil { 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 ee740fb70f1c..9114c7adb6d8 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.PointF 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 @@ abstract class CrossActivityBackAnimation( 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 @@ abstract class CrossActivityBackAnimation( 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 @@ abstract class CrossActivityBackAnimation( } // 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 @@ abstract class CrossActivityBackAnimation( 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 @@ abstract class CrossActivityBackAnimation( 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 @@ abstract class CrossActivityBackAnimation( 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 c4aafea50a62..c738ce542f8a 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 @@ class CustomCrossActivityBackAnimation( 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 @@ class CustomCrossActivityBackAnimation( } override fun getPreCommitEnteringBaseTransformation(progress: Float): Transformation { - gestureProgress = progress transformation.clear() enterAnimation!!.getTransformationAt(progress * PRE_COMMIT_MAX_PROGRESS, transformation) return transformation @@ -134,7 +132,13 @@ class CustomCrossActivityBackAnimation( 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 @@ class CustomCrossActivityBackAnimation( enteringTarget!!.leash, currentEnteringRect, enteringProgress, - enterAnimation!! + enterAnimation!!, + FlingMode.NO_FLING ) applyTransaction() } @@ -153,11 +158,12 @@ class CustomCrossActivityBackAnimation( 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 @@ class CustomCrossActivityBackAnimation( 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 44752fe0fa72..3b5eb3613d2a 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 @@ constructor( 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 @@ constructor( 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 a51ac633ad86..fa1091c63d00 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 @@ class BubbleBarExpandedViewDragController( draggedObject: MagnetizedObject<*> ) { isStuckToDismiss = true - pinController.setDropTargetHidden(true) + pinController.onStuckToDismissTarget() } override fun onUnstuckFromTarget( @@ -162,7 +162,6 @@ class BubbleBarExpandedViewDragController( ) { 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 e514f9d70599..eec24683db8a 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 @@ import com.android.wm.shell.common.bubbles.BubbleBarLocation.RIGHT */ 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 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi * @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 @@ abstract class BaseBubblePinController(private val screenSizeProvider: () -> Poi 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/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index ef384c74cb5e..ee7e4d191c44 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -486,7 +486,9 @@ class DesktopTasksController( "DesktopTasksController: cancelDragToDesktop taskId=%d", task.taskId ) - dragToDesktopTransitionHandler.cancelDragToDesktopTransition() + dragToDesktopTransitionHandler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL + ) } private fun moveToFullscreenWithAnimation(task: RunningTaskInfo, position: Point) { @@ -1105,20 +1107,31 @@ class DesktopTasksController( @JvmOverloads fun requestSplit( taskInfo: RunningTaskInfo, - leftOrTop: Boolean = false, + leftOrTop: Boolean = false ) { - val windowingMode = taskInfo.windowingMode - if ( - windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM - ) { - val wct = WindowContainerTransaction() - addMoveToSplitChanges(wct, taskInfo) - splitScreenController.requestEnterSplitSelect( - taskInfo, - wct, - if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT, - taskInfo.configuration.windowConfiguration.bounds - ) + // If a drag to desktop is in progress, we want to enter split select + // even if the requesting task is already in split. + val isDragging = dragToDesktopTransitionHandler.inProgress + val shouldRequestSplit = taskInfo.isFullscreen || taskInfo.isFreeform || isDragging + if (shouldRequestSplit) { + if (isDragging) { + releaseVisualIndicator() + val cancelState = if (leftOrTop) { + DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT + } else { + DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT + } + dragToDesktopTransitionHandler.cancelDragToDesktopTransition(cancelState) + } else { + val wct = WindowContainerTransaction() + addMoveToSplitChanges(wct, taskInfo) + splitScreenController.requestEnterSplitSelect( + taskInfo, + wct, + if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT, + taskInfo.configuration.windowConfiguration.bounds + ) + } } } @@ -1247,7 +1260,10 @@ class DesktopTasksController( * @param taskInfo the task being dragged. * @param y height of drag, to be checked against status bar height. */ - fun onDragPositioningEndThroughStatusBar(inputCoordinates: PointF, taskInfo: RunningTaskInfo) { + fun onDragPositioningEndThroughStatusBar( + inputCoordinates: PointF, + taskInfo: RunningTaskInfo, + ) { val indicator = getVisualIndicator() ?: return val indicatorType = indicator.updateIndicatorType(inputCoordinates, taskInfo.windowingMode) when (indicatorType) { @@ -1264,10 +1280,10 @@ class DesktopTasksController( cancelDragToDesktop(taskInfo) } DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR -> { - finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.LEFT)) + requestSplit(taskInfo, leftOrTop = true) } DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> { - finalizeDragToDesktop(taskInfo, getSnapBounds(taskInfo, SnapPosition.RIGHT)) + requestSplit(taskInfo, leftOrTop = false) } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt index 98c79d7174a9..d99b724c936f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandler.kt @@ -4,6 +4,7 @@ import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.animation.RectEvaluator import android.animation.ValueAnimator +import android.app.ActivityManager.RunningTaskInfo import android.app.ActivityOptions import android.app.ActivityOptions.SourceInfo import android.app.ActivityTaskManager.INVALID_TASK_ID @@ -12,9 +13,11 @@ import android.app.PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT import android.app.PendingIntent.FLAG_MUTABLE import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM +import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW import android.content.Context import android.content.Intent import android.content.Intent.FILL_IN_COMPONENT +import android.graphics.PointF import android.graphics.Rect import android.os.Bundle import android.os.IBinder @@ -30,6 +33,7 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED +import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition import com.android.wm.shell.protolog.ShellProtoLogGroup import com.android.wm.shell.shared.TransitionUtil import com.android.wm.shell.splitscreen.SplitScreenController @@ -186,7 +190,7 @@ class DragToDesktopTransitionHandler( * outside the desktop drop zone and is instead dropped back into the status bar region that * means the user wants to remain in their current windowing mode. */ - fun cancelDragToDesktopTransition() { + fun cancelDragToDesktopTransition(cancelState: CancelState) { if (!inProgress) { // Don't attempt to cancel a drag to desktop transition since there is no transition in // progress which means that the drag to desktop transition was never successfully @@ -200,13 +204,32 @@ class DragToDesktopTransitionHandler( clearState() return } - state.cancelled = true - if (state.draggedTaskChange != null) { + state.cancelState = cancelState + + if (state.draggedTaskChange != null && cancelState == CancelState.STANDARD_CANCEL) { // Regular case, transient launch of Home happened as is waiting for the cancel // transient to start and merge. Animate the cancellation (scale back to original // bounds) first before actually starting the cancel transition so that the wallpaper // is visible behind the animating task. startCancelAnimation() + } else if ( + state.draggedTaskChange != null && + (cancelState == CancelState.CANCEL_SPLIT_LEFT || + cancelState == CancelState.CANCEL_SPLIT_RIGHT) + ) { + // We have a valid dragged task, but the animation will be handled by + // SplitScreenController; request the transition here. + @SplitPosition val splitPosition = if (cancelState == CancelState.CANCEL_SPLIT_LEFT) { + SPLIT_POSITION_TOP_OR_LEFT + } else { + SPLIT_POSITION_BOTTOM_OR_RIGHT + } + val wct = WindowContainerTransaction() + restoreWindowOrder(wct, state) + state.startTransitionFinishTransaction?.apply() + state.startTransitionFinishCb?.onTransitionFinished(null /* wct */) + requestSplitFromScaledTask(splitPosition, wct) + clearState() } else { // There's no dragged task, this can happen when the "cancel" happened too quickly // before the "start" transition is even ready (like on a fling gesture). The @@ -217,6 +240,54 @@ class DragToDesktopTransitionHandler( } } + /** Calculate the bounds of a scaled task, then use those bounds to request split select. */ + private fun requestSplitFromScaledTask( + @SplitPosition splitPosition: Int, + wct: WindowContainerTransaction + ) { + val state = requireTransitionState() + val taskInfo = state.draggedTaskChange?.taskInfo + ?: error("Expected non-null taskInfo") + val taskBounds = Rect(taskInfo.configuration.windowConfiguration.bounds) + val taskScale = state.dragAnimator.scale + val scaledWidth = taskBounds.width() * taskScale + val scaledHeight = taskBounds.height() * taskScale + val dragPosition = PointF(state.dragAnimator.position) + state.dragAnimator.cancelAnimator() + val animatedTaskBounds = Rect( + dragPosition.x.toInt(), + dragPosition.y.toInt(), + (dragPosition.x + scaledWidth).toInt(), + (dragPosition.y + scaledHeight).toInt() + ) + requestSplitSelect(wct, taskInfo, splitPosition, animatedTaskBounds) + } + + private fun requestSplitSelect( + wct: WindowContainerTransaction, + taskInfo: RunningTaskInfo, + @SplitPosition splitPosition: Int, + taskBounds: Rect = Rect(taskInfo.configuration.windowConfiguration.bounds) + ) { + // Prepare to exit split in order to enter split select. + if (taskInfo.windowingMode == WINDOWING_MODE_MULTI_WINDOW) { + splitScreenController.prepareExitSplitScreen( + wct, + splitScreenController.getStageOfTask(taskInfo.taskId), + SplitScreenController.EXIT_REASON_DESKTOP_MODE + ) + splitScreenController.transitionHandler.onSplitToDesktop() + } + wct.setWindowingMode(taskInfo.token, WINDOWING_MODE_MULTI_WINDOW) + wct.setDensityDpi(taskInfo.token, context.resources.displayMetrics.densityDpi) + splitScreenController.requestEnterSplitSelect( + taskInfo, + wct, + splitPosition, + taskBounds + ) + } + override fun startAnimation( transition: IBinder, info: TransitionInfo, @@ -261,7 +332,7 @@ class DragToDesktopTransitionHandler( is TransitionState.FromSplit -> { state.splitRootChange = change val layer = - if (!state.cancelled) { + if (state.cancelState == CancelState.NO_CANCEL) { // Normal case, split root goes to the bottom behind everything // else. appLayers - i @@ -311,8 +382,18 @@ class DragToDesktopTransitionHandler( // Do not do this in the cancel-early case though, since in that case nothing should // happen on screen so the layering will remain the same as if no transition // occurred. - if (change.taskInfo?.taskId == state.draggedTaskId && !state.cancelled) { + if ( + change.taskInfo?.taskId == state.draggedTaskId && + state.cancelState != CancelState.STANDARD_CANCEL + ) { + // We need access to the dragged task's change in both non-cancel and split + // cancel cases. state.draggedTaskChange = change + } + if ( + change.taskInfo?.taskId == state.draggedTaskId && + state.cancelState == CancelState.NO_CANCEL + ) { taskDisplayAreaOrganizer.reparentToDisplayArea( change.endDisplayId, change.leash, @@ -331,11 +412,11 @@ class DragToDesktopTransitionHandler( state.startTransitionFinishTransaction = finishTransaction startTransaction.apply() - if (!state.cancelled) { + if (state.cancelState == CancelState.NO_CANCEL) { // Normal case, start animation to scale down the dragged task. It'll also be moved to // follow the finger and when released we'll start the next phase/transition. state.dragAnimator.startAnimation() - } else { + } else if (state.cancelState == CancelState.STANDARD_CANCEL) { // Cancel-early case, the state was flagged was cancelled already, which means the // gesture ended in the cancel region. This can happen even before the start transition // is ready/animate here when cancelling quickly like with a fling. There's no point @@ -343,6 +424,26 @@ class DragToDesktopTransitionHandler( // directly into starting the cancel transition to restore WM order. Surfaces should // not move as if no transition happened. startCancelDragToDesktopTransition() + } else if ( + state.cancelState == CancelState.CANCEL_SPLIT_LEFT || + state.cancelState == CancelState.CANCEL_SPLIT_RIGHT + ){ + // Cancel-early case for split-cancel. The state was flagged already as a cancel for + // requesting split select. Similar to the above, this can happen due to quick fling + // gestures. We can simply request split here without needing to calculate animated + // task bounds as the task has not shrunk at all. + val splitPosition = if (state.cancelState == CancelState.CANCEL_SPLIT_LEFT) { + SPLIT_POSITION_TOP_OR_LEFT + } else { + SPLIT_POSITION_BOTTOM_OR_RIGHT + } + val taskInfo = state.draggedTaskChange?.taskInfo + ?: error("Expected non-null task info.") + val wct = WindowContainerTransaction() + restoreWindowOrder(wct) + state.startTransitionFinishTransaction?.apply() + state.startTransitionFinishCb?.onTransitionFinished(null /* wct */) + requestSplitSelect(wct, taskInfo, splitPosition) } return true } @@ -355,6 +456,12 @@ class DragToDesktopTransitionHandler( finishCallback: Transitions.TransitionFinishCallback ) { val state = requireTransitionState() + // We don't want to merge the split select animation if that's what we requested. + if (state.cancelState == CancelState.CANCEL_SPLIT_LEFT || + state.cancelState == CancelState.CANCEL_SPLIT_RIGHT) { + clearState() + return + } val isCancelTransition = info.type == TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP && transition == state.cancelTransitionToken && @@ -552,6 +659,17 @@ class DragToDesktopTransitionHandler( private fun startCancelDragToDesktopTransition() { val state = requireTransitionState() val wct = WindowContainerTransaction() + restoreWindowOrder(wct, state) + state.cancelTransitionToken = + transitions.startTransition( + TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this + ) + } + + private fun restoreWindowOrder( + wct: WindowContainerTransaction, + state: TransitionState = requireTransitionState() + ) { when (state) { is TransitionState.FromFullscreen -> { // There may have been tasks sent behind home that are not the dragged task (like @@ -580,9 +698,6 @@ class DragToDesktopTransitionHandler( } val homeWc = state.homeToken ?: error("Home task should be non-null before cancelling") wct.restoreTransientOrder(homeWc) - - state.cancelTransitionToken = - transitions.startTransition(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP, wct, this) } private fun clearState() { @@ -624,7 +739,7 @@ class DragToDesktopTransitionHandler( abstract var cancelTransitionToken: IBinder? abstract var homeToken: WindowContainerToken? abstract var draggedTaskChange: Change? - abstract var cancelled: Boolean + abstract var cancelState: CancelState abstract var startAborted: Boolean data class FromFullscreen( @@ -636,7 +751,7 @@ class DragToDesktopTransitionHandler( override var cancelTransitionToken: IBinder? = null, override var homeToken: WindowContainerToken? = null, override var draggedTaskChange: Change? = null, - override var cancelled: Boolean = false, + override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, var otherRootChanges: MutableList<Change> = mutableListOf() ) : TransitionState() @@ -650,13 +765,25 @@ class DragToDesktopTransitionHandler( override var cancelTransitionToken: IBinder? = null, override var homeToken: WindowContainerToken? = null, override var draggedTaskChange: Change? = null, - override var cancelled: Boolean = false, + override var cancelState: CancelState = CancelState.NO_CANCEL, override var startAborted: Boolean = false, var splitRootChange: Change? = null, var otherSplitTask: Int ) : TransitionState() } + /** Enum to provide context on cancelling a drag to desktop event. */ + enum class CancelState { + /** No cancel case; this drag is not flagged for a cancel event. */ + NO_CANCEL, + /** A standard cancel event; should restore task to previous windowing mode. */ + STANDARD_CANCEL, + /** A cancel event where the task will request to enter split on the left side. */ + CANCEL_SPLIT_LEFT, + /** A cancel event where the task will request to enter split on the right side. */ + CANCEL_SPLIT_RIGHT + } + companion object { /** The duration of the animation to commit or cancel the drag-to-desktop gesture. */ private const val DRAG_TO_DESKTOP_FINISH_ANIM_DURATION_MS = 336L 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 c79eef7efb61..cd478e5bd567 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 class KeyguardTransitionHandler } 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/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 6fcea1fe5560..b52b0d8dee74 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -489,6 +489,14 @@ public class PipTransition extends PipTransitionController { // activity windowing mode, and set the task bounds to the final bounds wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED); wct.setBounds(taskInfo.token, destinationBounds); + // If the animation is only used to apply destination bounds immediately and + // invisibly, then reshow it until the pip is drawn with the bounds. + final PipAnimationController.PipTransitionAnimator<?> animator = + mPipAnimationController.getCurrentAnimator(); + if (animator != null && animator.getEndValue().equals(0f)) { + tx.addTransactionCommittedListener(mTransitions.getMainExecutor(), + () -> fadeExistingPip(true /* show */)); + } } else { wct.setBounds(taskInfo.token, null /* bounds */); } @@ -1026,6 +1034,7 @@ public class PipTransition extends PipTransitionController { } startTransaction.apply(); + int animationDuration = mEnterExitAnimationDuration; PipAnimationController.PipTransitionAnimator animator; if (enterAnimationType == ANIM_TYPE_BOUNDS) { animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds, @@ -1057,8 +1066,17 @@ public class PipTransition extends PipTransitionController { } } } else if (enterAnimationType == ANIM_TYPE_ALPHA) { + // In case augmentRequest() is unable to apply the entering bounds (e.g. the request + // info only contains display change), keep the animation invisible (alpha 0) and + // duration 0 to apply the destination bounds. The actual fade-in animation will be + // done in onFinishResize() after the bounds are applied. + final boolean fadeInAfterOnFinishResize = rotationDelta != Surface.ROTATION_0 + && mFixedRotationState == FIXED_ROTATION_CALLBACK; animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds, - 0f, 1f); + 0f, fadeInAfterOnFinishResize ? 0f : 1f); + if (fadeInAfterOnFinishResize) { + animationDuration = 0; + } mSurfaceTransactionHelper .crop(finishTransaction, leash, destinationBounds) .round(finishTransaction, leash, true /* applyCornerRadius */); @@ -1068,7 +1086,7 @@ public class PipTransition extends PipTransitionController { mPipOrganizer.setContentOverlay(animator.getContentOverlayLeash(), currentBounds); animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) .setPipAnimationCallback(mPipAnimationCallback) - .setDuration(mEnterExitAnimationDuration); + .setDuration(animationDuration); if (rotationDelta != Surface.ROTATION_0 && mFixedRotationState == FIXED_ROTATION_TRANSITION) { // For fixed rotation, the animation destination bounds is in old rotation coordinates. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java new file mode 100644 index 000000000000..3e298e530415 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipAlphaAnimator.java @@ -0,0 +1,110 @@ +/* + * 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.wm.shell.pip2.animation; + +import android.animation.Animator; +import android.animation.ValueAnimator; +import android.annotation.IntDef; +import android.view.SurfaceControl; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.wm.shell.pip2.PipSurfaceTransactionHelper; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Animator that handles the alpha animation for entering PIP + */ +public class PipAlphaAnimator extends ValueAnimator implements ValueAnimator.AnimatorUpdateListener, + ValueAnimator.AnimatorListener { + @IntDef(prefix = {"FADE_"}, value = { + FADE_IN, + FADE_OUT + }) + + @Retention(RetentionPolicy.SOURCE) + public @interface Fade {} + + public static final int FADE_IN = 0; + public static final int FADE_OUT = 1; + + private final SurfaceControl mLeash; + private final SurfaceControl.Transaction mStartTransaction; + + // optional callbacks for tracking animation start and end + @Nullable private Runnable mAnimationStartCallback; + @Nullable private Runnable mAnimationEndCallback; + + private final PipSurfaceTransactionHelper.SurfaceControlTransactionFactory + mSurfaceControlTransactionFactory; + + public PipAlphaAnimator(SurfaceControl leash, + SurfaceControl.Transaction tx, + @Fade int direction) { + mLeash = leash; + mStartTransaction = tx; + if (direction == FADE_IN) { + setFloatValues(0f, 1f); + } else { // direction == FADE_OUT + setFloatValues(1f, 0f); + } + mSurfaceControlTransactionFactory = + new PipSurfaceTransactionHelper.VsyncSurfaceControlTransactionFactory(); + addListener(this); + addUpdateListener(this); + } + + public void setAnimationStartCallback(@NonNull Runnable runnable) { + mAnimationStartCallback = runnable; + } + + public void setAnimationEndCallback(@NonNull Runnable runnable) { + mAnimationEndCallback = runnable; + } + + @Override + public void onAnimationStart(@NonNull Animator animation) { + if (mAnimationStartCallback != null) { + mAnimationStartCallback.run(); + } + if (mStartTransaction != null) { + mStartTransaction.apply(); + } + } + + @Override + public void onAnimationUpdate(@NonNull ValueAnimator animation) { + final float alpha = (Float) animation.getAnimatedValue(); + mSurfaceControlTransactionFactory.getTransaction().setAlpha(mLeash, alpha).apply(); + } + + @Override + public void onAnimationEnd(@NonNull Animator animation) { + if (mAnimationEndCallback != null) { + mAnimationEndCallback.run(); + } + } + + @Override + public void onAnimationCancel(@NonNull Animator animation) {} + + @Override + public void onAnimationRepeat(@NonNull Animator animation) {} +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index 7dddd2748f83..3e215d9bfa34 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -52,6 +52,7 @@ import com.android.wm.shell.common.pip.PipMenuController; import com.android.wm.shell.common.pip.PipUtils; import com.android.wm.shell.pip.PipContentOverlay; import com.android.wm.shell.pip.PipTransitionController; +import com.android.wm.shell.pip2.animation.PipAlphaAnimator; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.transition.Transitions; @@ -392,8 +393,14 @@ public class PipTransition extends PipTransitionController implements // cache the PiP task token and leash WindowContainerToken pipTaskToken = pipChange.getContainer(); - startTransaction.apply(); - finishCallback.onTransitionFinished(null); + Preconditions.checkNotNull(mPipLeash, "Leash is null for alpha transition."); + // start transition with 0 alpha + startTransaction.setAlpha(mPipLeash, 0f); + PipAlphaAnimator animator = new PipAlphaAnimator(mPipLeash, + startTransaction, PipAlphaAnimator.FADE_IN); + animator.setAnimationEndCallback(() -> finishCallback.onTransitionFinished(null)); + + animator.start(); return true; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java index 9ce22094d56b..e196254628d0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java @@ -167,7 +167,7 @@ class ScreenRotationAnimation { t.show(mScreenshotLayer); if (!isCustomRotate()) { mStartLuma = TransitionAnimation.getBorderLuma(hardwareBuffer, - screenshotBuffer.getColorSpace()); + screenshotBuffer.getColorSpace(), mSurfaceControl); } hardwareBuffer.close(); } 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 6224543516fa..6ade81c0f3a1 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 class Transitions implements RemoteCallable<Transitions>, 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 6901e75019bc..37cdbb47bfe8 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 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel { } 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 78f0ef7af45c..4f049015af99 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 @@ class MaximizeButtonView( } 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 ec204714c341..7ade9876d28a 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 @@ import android.view.WindowInsetsController.APPEARANCE_TRANSPARENT_CAPTION_BAR_BA 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 285e5b6a04a5..51b291c0b7a4 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 @@ import org.junit.runner.RunWith 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/desktopmode/DragToDesktopTransitionHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt index 2ade3fba9b08..bbf523bc40d2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DragToDesktopTransitionHandlerTest.kt @@ -18,6 +18,8 @@ import androidx.test.filters.SmallTest import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.ShellTestCase import com.android.wm.shell.TestRunningTaskInfoBuilder +import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT +import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT import com.android.wm.shell.splitscreen.SplitScreenController import com.android.wm.shell.transition.Transitions import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP @@ -48,6 +50,7 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Mock private lateinit var transitions: Transitions @Mock private lateinit var taskDisplayAreaOrganizer: RootTaskDisplayAreaOrganizer @Mock private lateinit var splitScreenController: SplitScreenController + @Mock private lateinit var dragAnimator: MoveToDesktopAnimator private val transactionSupplier = Supplier { mock<SurfaceControl.Transaction>() } @@ -68,7 +71,6 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun startDragToDesktop_animateDragWhenReady() { val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() // Simulate transition is started. val transition = startDragToDesktopTransition(task, dragAnimator) @@ -90,36 +92,36 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun startDragToDesktop_cancelledBeforeReady_startCancelTransition() { - val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() - // Simulate transition is started and is ready to animate. - val transition = startDragToDesktopTransition(task, dragAnimator) - - handler.cancelDragToDesktopTransition() + performEarlyCancel(DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL) + verify(transitions) + .startTransition(eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), any(), eq(handler)) + } - handler.startAnimation( - transition = transition, - info = - createTransitionInfo( - type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, - draggedTask = task - ), - startTransaction = mock(), - finishTransaction = mock(), - finishCallback = {} + @Test + fun startDragToDesktop_cancelledBeforeReady_verifySplitLeftCancel() { + performEarlyCancel(DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT) + verify(splitScreenController).requestEnterSplitSelect( + any(), + any(), + eq(SPLIT_POSITION_TOP_OR_LEFT), + any() ) + } - // Don't even animate the "drag" since it was already cancelled. - verify(dragAnimator, never()).startAnimation() - // Instead, start the cancel transition. - verify(transitions) - .startTransition(eq(TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP), any(), eq(handler)) + @Test + fun startDragToDesktop_cancelledBeforeReady_verifySplitRightCancel() { + performEarlyCancel(DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT) + verify(splitScreenController).requestEnterSplitSelect( + any(), + any(), + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), + any() + ) } @Test fun startDragToDesktop_aborted_finishDropped() { val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() // Simulate transition is started. val transition = startDragToDesktopTransition(task, dragAnimator) // But the transition was aborted. @@ -137,14 +139,15 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun startDragToDesktop_aborted_cancelDropped() { val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() // Simulate transition is started. val transition = startDragToDesktopTransition(task, dragAnimator) // But the transition was aborted. handler.onTransitionConsumed(transition, aborted = true, mock()) // Attempt to finish the failed drag start. - handler.cancelDragToDesktopTransition() + handler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL + ) // Should not be attempted and state should be reset. assertFalse(handler.inProgress) @@ -153,7 +156,6 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun startDragToDesktop_anotherTransitionInProgress_startDropped() { val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() // Simulate attempt to start two drag to desktop transitions. startDragToDesktopTransition(task, dragAnimator) @@ -169,39 +171,63 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun cancelDragToDesktop_startWasReady_cancel() { - val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() - whenever(dragAnimator.position).thenReturn(PointF()) - // Simulate transition is started and is ready to animate. - val transition = startDragToDesktopTransition(task, dragAnimator) - handler.startAnimation( - transition = transition, - info = - createTransitionInfo( - type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, - draggedTask = task - ), - startTransaction = mock(), - finishTransaction = mock(), - finishCallback = {} - ) + startDrag() // Then user cancelled after it had already started. - handler.cancelDragToDesktopTransition() + handler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL + ) // Cancel animation should run since it had already started. verify(dragAnimator).cancelAnimator() } @Test + fun cancelDragToDesktop_splitLeftCancelType_splitRequested() { + startDrag() + + // Then user cancelled it, requesting split. + handler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_LEFT + ) + + // Verify the request went through split controller. + verify(splitScreenController).requestEnterSplitSelect( + any(), + any(), + eq(SPLIT_POSITION_TOP_OR_LEFT), + any() + ) + } + + @Test + fun cancelDragToDesktop_splitRightCancelType_splitRequested() { + startDrag() + + // Then user cancelled it, requesting split. + handler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.CANCEL_SPLIT_RIGHT + ) + + // Verify the request went through split controller. + verify(splitScreenController).requestEnterSplitSelect( + any(), + any(), + eq(SPLIT_POSITION_BOTTOM_OR_RIGHT), + any() + ) + } + + @Test fun cancelDragToDesktop_startWasNotReady_animateCancel() { val task = createTask() - val dragAnimator = mock<MoveToDesktopAnimator>() // Simulate transition is started and is ready to animate. startDragToDesktopTransition(task, dragAnimator) // Then user cancelled before the transition was ready and animated. - handler.cancelDragToDesktopTransition() + handler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL + ) // No need to animate the cancel since the start animation couldn't even start. verifyZeroInteractions(dragAnimator) @@ -210,7 +236,9 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { @Test fun cancelDragToDesktop_transitionNotInProgress_dropCancel() { // Then cancel is called before the transition was started. - handler.cancelDragToDesktopTransition() + handler.cancelDragToDesktopTransition( + DragToDesktopTransitionHandler.CancelState.STANDARD_CANCEL + ) // Verify cancel is dropped. verify(transitions, never()).startTransition( @@ -233,6 +261,24 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { ) } + private fun startDrag() { + val task = createTask() + whenever(dragAnimator.position).thenReturn(PointF()) + // Simulate transition is started and is ready to animate. + val transition = startDragToDesktopTransition(task, dragAnimator) + handler.startAnimation( + transition = transition, + info = + createTransitionInfo( + type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, + draggedTask = task + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + } + private fun startDragToDesktopTransition( task: RunningTaskInfo, dragAnimator: MoveToDesktopAnimator @@ -250,6 +296,29 @@ class DragToDesktopTransitionHandlerTest : ShellTestCase() { return token } + private fun performEarlyCancel(cancelState: DragToDesktopTransitionHandler.CancelState) { + val task = createTask() + // Simulate transition is started and is ready to animate. + val transition = startDragToDesktopTransition(task, dragAnimator) + + handler.cancelDragToDesktopTransition(cancelState) + + handler.startAnimation( + transition = transition, + info = + createTransitionInfo( + type = TRANSIT_DESKTOP_MODE_START_DRAG_TO_DESKTOP, + draggedTask = task + ), + startTransaction = mock(), + finishTransaction = mock(), + finishCallback = {} + ) + + // Don't even animate the "drag" since it was already cancelled. + verify(dragAnimator, never()).startAnimation() + } + private fun createTask( @WindowingMode windowingMode: Int = WINDOWING_MODE_FULLSCREEN, isHome: Boolean = false, 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 3ca9b57e03fd..a731e5394bdf 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 class DesktopModeWindowDecorationTests extends ShellTestCase { 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 class DesktopModeWindowDecorationTests extends ShellTestCase { 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 5ce990fdeb82..7b7ccf51aa1a 100644 --- a/libs/input/Android.bp +++ b/libs/input/Android.bp @@ -48,7 +48,6 @@ cc_library_shared { "libgui", "libui", "libinput", - "libnativewindow", ], header_libs: [ diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index f1ee3256dbee..eecc741a3bbb 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -165,6 +165,15 @@ void MouseCursorController::setStylusHoverMode(bool stylusHoverMode) { } } +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 @@ void MouseCursorController::updatePointerLocked() REQUIRES(mLock) { 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 dc7e8ca16c8a..78f6413ff111 100644 --- a/libs/input/MouseCursorController.h +++ b/libs/input/MouseCursorController.h @@ -53,6 +53,9 @@ public: 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 @@ private: 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 cca1b07c3118..11b27a214984 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -286,13 +286,16 @@ void PointerController::setCustomPointerIcon(const SpriteIcon& icon) { 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 c6430f7f36ff..4d1e1d733cc1 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -66,7 +66,8 @@ public: 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 2dcb1f1d1650..5b00fca4d857 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -162,6 +162,16 @@ public: }; 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 @@ protected: 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 @@ TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) { 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 @@ TEST_F(PointerControllerTest, updatesSkipScreenshotFlagForTouchSpots) { 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 293c561f166c..d148afd3c15d 100644 --- a/media/java/android/media/AudioSystem.java +++ b/media/java/android/media/AudioSystem.java @@ -1764,6 +1764,10 @@ public class AudioSystem 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 70462effaa54..442ccdcddb2b 100644 --- a/media/java/android/media/session/MediaController.java +++ b/media/java/android/media/session/MediaController.java @@ -141,10 +141,9 @@ public final class MediaController { } 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 final class MediaController { 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 final class MediaController { 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 @@ public final class MediaController { 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 final class MediaController { 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 final class MediaController { 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 final class MediaController { 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 final class MediaController { 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 final class MediaController { 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 @@ public final class MediaController { // 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 @@ public final class MediaController { // 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 @@ public final class MediaController { } 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 @@ public final class MediaController { 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 @@ public final class MediaController { // 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 @@ public final class MediaController { 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 @@ public final class MediaController { 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 @@ public final class MediaController { 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 final class MediaController { 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 @@ public final class MediaController { } 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 @@ public final class MediaController { } 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 @@ public final class MediaController { } 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 final class MediaController { 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 @@ public final class MediaController { } 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 @@ public final class MediaController { } 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 @@ public final class MediaController { } 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 final class MediaController { 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 final class MediaController { 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 final class MediaController { 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 final class MediaController { 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 final class MediaController { 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 final class MediaController { 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 final class MediaController { 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 final class MediaController { 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 final class MediaController { 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 @@ public final class MediaController { } 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 @@ public final class MediaController { } 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 a33e2252019b..055ccbced58a 100644 --- a/nfc/api/system-current.txt +++ b/nfc/api/system-current.txt @@ -27,6 +27,7 @@ package android.nfc { 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 698df28129be..1dfc81e2108e 100644 --- a/nfc/java/android/nfc/NfcAdapter.java +++ b/nfc/java/android/nfc/NfcAdapter.java @@ -340,7 +340,8 @@ public final class NfcAdapter { 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 final class NfcAdapter { 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 final class NfcAdapter { 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 final class NfcAdapter { 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 @@ public final class NfcAdapter { } } } - 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 4c76fb02f7d8..5dcc84ccf8b9 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 @@ import java.util.List; /** * 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 @@ public final class PollingFrame implements Parcelable{ private final int mType; private final byte[] mData; private final int mGain; - @DurationMillisLong private final long mTimestamp; private boolean mTriggeredAutoTransact; @@ -179,18 +177,18 @@ public final class PollingFrame implements Parcelable{ * @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 @@ public final class PollingFrame implements Parcelable{ * 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 @@ public final class PollingFrame implements Parcelable{ } /** - * 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 cb2a48c2913f..b242a76ffae4 100644 --- a/nfc/java/android/nfc/flags.aconfig +++ b/nfc/java/android/nfc/flags.aconfig @@ -101,3 +101,12 @@ flag { 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 9c8ec3b56813..3022fa01cd8d 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.AuthenticationEntry 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 @@ private fun getCredentialOptionInfoList( * 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 - // 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) + 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 } - return null + + val allowedAuthenticators: Int = biometricPromptDataBundle.getInt(allowedAuthConstantKey) + + // This is optional and does not affect validating the biometric flow in any case + 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 486cfe7123dd..fe4beadfa9ec 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 @@ package com.android.credentialmanager.model * 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 7bc25ed81089..894d5ef24ebd 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt @@ -135,16 +135,18 @@ class CredentialSelectorViewModel( 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 b43b5f318cf1..c35721c11741 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.EntryInfo import com.android.credentialmanager.model.creation.CreateOptionInfo import com.android.credentialmanager.model.get.CredentialEntryInfo import com.android.credentialmanager.model.get.ProviderInfo -import java.lang.Exception /** * Aggregates common display information used for the Biometric Flow. @@ -121,11 +120,11 @@ fun runBiometricFlowForGet( 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 @@ fun runBiometricFlowForGet( if (biometricDisplayInfo == null) { onBiometricFailureFallback(BiometricFlowType.GET) - return + return false } val callback: BiometricPrompt.AuthenticationCallback = @@ -146,7 +145,7 @@ fun runBiometricFlowForGet( 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 @@ fun runBiometricFlowForCreate( 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 @@ fun runBiometricFlowForCreate( if (biometricDisplayInfo == null) { onBiometricFailureFallback(BiometricFlowType.CREATE) - return + return false } val callback: BiometricPrompt.AuthenticationCallback = @@ -193,7 +192,7 @@ fun runBiometricFlowForCreate( 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 @@ fun runBiometricFlowForCreate( * 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 @@ private fun runBiometricFlow( 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 @@ private fun runBiometricFlow( } 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 3c80113134b1..cb089adf750e 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 @@ class Constants { 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 7d61f73a525b..4993a1fa0672 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 @@ fun CreateCredentialScreen( 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 @@ internal fun BiometricSelectionPage( 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 @@ internal fun BiometricSelectionPage( 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 ba61b90fa4dc..517ad0069f85 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 @@ fun GetCredentialScreen( onBiometricPromptStateChange = viewModel::onBiometricPromptStateChange, getBiometricCancellationSignal = - viewModel::getBiometricCancellationSignal + viewModel::getBiometricCancellationSignal, + onLog = { viewModel.logUiEvent(it) }, ) } else if (credmanBiometricApiEnabled() && getCredentialUiState.currentScreenState @@ -260,12 +261,13 @@ internal fun BiometricSelectionPage( 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 @@ internal fun BiometricSelectionPage( 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 daa42be020ce..dac25fa165af 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 @@ enum class CreateCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnu 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 8de8895e8ffc..8870f28b9fcc 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 @@ enum class GetCredentialEvent(private val id: Int) : UiEventLogger.UiEventEnum { 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/DynamicSystemInstallationService/Android.bp b/packages/DynamicSystemInstallationService/Android.bp index b8f54b3faf63..ae6901917630 100644 --- a/packages/DynamicSystemInstallationService/Android.bp +++ b/packages/DynamicSystemInstallationService/Android.bp @@ -30,10 +30,6 @@ android_app { certificate: "platform", privileged: true, platform_apis: true, - - optimize: { - enabled: false, - }, } java_library { diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java index 59a511db5b3a..10e8246db84f 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallInstalling.java @@ -311,18 +311,18 @@ public class InstallInstalling extends Activity { 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/PackageInstaller/src/com/android/packageinstaller/InstallStart.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java index 3fea5996e3ef..379dfe32cc51 100644 --- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java +++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallStart.java @@ -125,13 +125,14 @@ public class InstallStart extends Activity { -1, callingUid) == PackageManager.PERMISSION_GRANTED; boolean isSystemDownloadsProvider = PackageUtil.getSystemDownloadsProviderInfo( mPackageManager, callingUid) != null; - boolean isTrustedSource = false; - if (sourceInfo != null && sourceInfo.isPrivilegedApp()) { - isTrustedSource = intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false) || ( - callingUid != Process.INVALID_UID && checkPermission( - Manifest.permission.INSTALL_PACKAGES, -1 /* pid */, callingUid) - == PackageManager.PERMISSION_GRANTED); - } + + boolean isPrivilegedAndKnown = (sourceInfo != null && sourceInfo.isPrivilegedApp()) && + intent.getBooleanExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, false); + boolean isInstallPkgPermissionGranted = + checkPermission(Manifest.permission.INSTALL_PACKAGES, /* pid= */ -1, callingUid) + == PackageManager.PERMISSION_GRANTED; + + boolean isTrustedSource = isPrivilegedAndKnown || isInstallPkgPermissionGranted; if (!isTrustedSource && !isSystemDownloadsProvider && !isDocumentsManager && callingUid != Process.INVALID_UID) { @@ -154,7 +155,7 @@ public class InstallStart extends Activity { mAbortInstall = true; } - checkDevicePolicyRestrictions(); + checkDevicePolicyRestrictions(isTrustedSource); final String installerPackageNameFromIntent = getIntent().getStringExtra( Intent.EXTRA_INSTALLER_PACKAGE_NAME); @@ -304,12 +305,17 @@ public class InstallStart extends Activity { return callingUid == installerUid; } - private void checkDevicePolicyRestrictions() { - final String[] restrictions = new String[] { - UserManager.DISALLOW_INSTALL_APPS, - UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, - UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY - }; + private void checkDevicePolicyRestrictions(boolean isTrustedSource) { + String[] restrictions; + if(isTrustedSource) { + restrictions = new String[] { UserManager.DISALLOW_INSTALL_APPS }; + } else { + restrictions = new String[] { + UserManager.DISALLOW_INSTALL_APPS, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES, + UserManager.DISALLOW_INSTALL_UNKNOWN_SOURCES_GLOBALLY + }; + } final DevicePolicyManager dpm = getSystemService(DevicePolicyManager.class); for (String restriction : restrictions) { diff --git a/packages/SettingsLib/Color/res/values/colors.xml b/packages/SettingsLib/Color/res/values/colors.xml index ef0dd1b654b9..709752362153 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/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt index 82423473e682..b4a91726ac1d 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/BackupRestoreStorageManager.kt @@ -138,7 +138,7 @@ class BackupRestoreStorageManager private constructor(private val application: A private fun notifyBackupManager(key: Any?, reason: Int) { val name = storage.name // prefer not triggering backup immediately after restore - if (reason == ChangeReason.RESTORE) { + if (reason == DataChangeReason.RESTORE) { Log.d( LOG_TAG, "Notify BackupManager dataChanged ignored for restore: storage=$name key=$key" @@ -161,8 +161,8 @@ class BackupRestoreStorageManager private constructor(private val application: A fun notifyRestoreFinished() { when (storage) { - is KeyedObservable<*> -> storage.notifyChange(ChangeReason.RESTORE) - is Observable -> storage.notifyChange(ChangeReason.RESTORE) + is KeyedObservable<*> -> storage.notifyChange(DataChangeReason.RESTORE) + is Observable -> storage.notifyChange(DataChangeReason.RESTORE) } } } diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/DataChangeReason.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/DataChangeReason.kt new file mode 100644 index 000000000000..145fabea52af --- /dev/null +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/DataChangeReason.kt @@ -0,0 +1,43 @@ +/* + * 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.settingslib.datastore + +import androidx.annotation.IntDef + +/** The reason of data change. */ +@IntDef( + DataChangeReason.UNKNOWN, + DataChangeReason.UPDATE, + DataChangeReason.DELETE, + DataChangeReason.RESTORE, + DataChangeReason.SYNC_ACROSS_PROFILES, +) +@Retention(AnnotationRetention.SOURCE) +annotation class DataChangeReason { + companion object { + /** Unknown reason of the change. */ + const val UNKNOWN = 0 + /** Data is updated. */ + const val UPDATE = 1 + /** Data is deleted. */ + const val DELETE = 2 + /** Data is restored from backup/restore framework. */ + const val RESTORE = 3 + /** Data is synced from another profile (e.g. personal profile to work profile). */ + const val SYNC_ACROSS_PROFILES = 4 + } +} diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt index 3ed4d46459cf..ede7c63d00b4 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/KeyedObserver.kt @@ -37,7 +37,7 @@ fun interface KeyedObserver<in K> { * @param reason the reason of change * @see KeyedObservable.addObserver */ - fun onKeyChanged(key: K, @ChangeReason reason: Int) + fun onKeyChanged(key: K, reason: Int) } /** @@ -89,7 +89,7 @@ interface KeyedObservable<K> { * * @param reason reason of the change */ - fun notifyChange(@ChangeReason reason: Int) + fun notifyChange(reason: Int) /** * Notifies observers that a change occurs on given key. @@ -99,7 +99,7 @@ interface KeyedObservable<K> { * @param key key of the change * @param reason reason of the change */ - fun notifyChange(key: K, @ChangeReason reason: Int) + fun notifyChange(key: K, reason: Int) } /** A thread safe implementation of [KeyedObservable]. */ @@ -141,7 +141,7 @@ class KeyedDataObservable<K> : KeyedObservable<K> { } } - override fun notifyChange(@ChangeReason reason: Int) { + override fun notifyChange(reason: Int) { // make a copy to avoid potential ConcurrentModificationException val observers = synchronized(observers) { observers.entries.toTypedArray() } val keyedObservers = synchronized(keyedObservers) { keyedObservers.copy() } @@ -165,7 +165,7 @@ class KeyedDataObservable<K> : KeyedObservable<K> { return result } - override fun notifyChange(key: K, @ChangeReason reason: Int) { + override fun notifyChange(key: K, reason: Int) { // make a copy to avoid potential ConcurrentModificationException val observers = synchronized(observers) { observers.entries.toTypedArray() } val keyedObservers = diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt index 6d0ca6690c9f..98d0f6e3f9a1 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/Observer.kt @@ -18,34 +18,9 @@ package com.android.settingslib.datastore import androidx.annotation.AnyThread import androidx.annotation.GuardedBy -import androidx.annotation.IntDef import java.util.WeakHashMap import java.util.concurrent.Executor -/** The reason of a change. */ -@IntDef( - ChangeReason.UNKNOWN, - ChangeReason.UPDATE, - ChangeReason.DELETE, - ChangeReason.RESTORE, - ChangeReason.SYNC_ACROSS_PROFILES, -) -@Retention(AnnotationRetention.SOURCE) -annotation class ChangeReason { - companion object { - /** Unknown reason of the change. */ - const val UNKNOWN = 0 - /** Data is updated. */ - const val UPDATE = 1 - /** Data is deleted. */ - const val DELETE = 2 - /** Data is restored from backup/restore framework. */ - const val RESTORE = 3 - /** Data is synced from another profile (e.g. personal profile to work profile). */ - const val SYNC_ACROSS_PROFILES = 4 - } -} - /** * Callback to be informed of changes in [Observable] object. * @@ -60,7 +35,7 @@ fun interface Observer { * @param reason the reason of change * @see [Observable.addObserver] for the notices. */ - fun onChanged(@ChangeReason reason: Int) + fun onChanged(reason: Int) } /** An observable object allows to observe change with [Observer]. */ @@ -90,7 +65,7 @@ interface Observable { * * @param reason reason of the change */ - fun notifyChange(@ChangeReason reason: Int) + fun notifyChange(reason: Int) } /** A thread safe implementation of [Observable]. */ @@ -110,7 +85,7 @@ class DataObservable : Observable { synchronized(observers) { observers.remove(observer) } } - override fun notifyChange(@ChangeReason reason: Int) { + override fun notifyChange(reason: Int) { // make a copy to avoid potential ConcurrentModificationException val entries = synchronized(observers) { observers.entries.toTypedArray() } for (entry in entries) { diff --git a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt index 9f9c0d839744..20a95d7efc4b 100644 --- a/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt +++ b/packages/SettingsLib/DataStore/src/com/android/settingslib/datastore/SharedPreferencesStorage.kt @@ -83,10 +83,10 @@ constructor( private val sharedPreferencesListener = SharedPreferences.OnSharedPreferenceChangeListener { _, key -> if (key != null) { - notifyChange(key, ChangeReason.UPDATE) + notifyChange(key, DataChangeReason.UPDATE) } else { // On Android >= R, SharedPreferences.Editor.clear() will trigger this case - notifyChange(ChangeReason.DELETE) + notifyChange(DataChangeReason.DELETE) } } diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt index d8f502854402..19c574a843ca 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/BackupRestoreStorageManagerTest.kt @@ -157,9 +157,9 @@ class BackupRestoreStorageManagerTest { manager.onRestoreFinished() - verify(keyedObserver).onKeyChanged("key", ChangeReason.RESTORE) - verify(anyKeyObserver).onKeyChanged(null, ChangeReason.RESTORE) - verify(observer).onChanged(ChangeReason.RESTORE) + verify(keyedObserver).onKeyChanged("key", DataChangeReason.RESTORE) + verify(anyKeyObserver).onKeyChanged(null, DataChangeReason.RESTORE) + verify(observer).onChanged(DataChangeReason.RESTORE) if (isRobolectric()) { Shadows.shadowOf(BackupManager(application)).apply { assertThat(isDataChanged).isFalse() @@ -186,8 +186,8 @@ class BackupRestoreStorageManagerTest { assertThat(dataChangedCount).isEqualTo(0) } - fileStorage.notifyChange(ChangeReason.UPDATE) - verify(observer).onChanged(ChangeReason.UPDATE) + fileStorage.notifyChange(DataChangeReason.UPDATE) + verify(observer).onChanged(DataChangeReason.UPDATE) verify(keyedObserver, never()).onKeyChanged(any(), any()) verify(anyKeyObserver, never()).onKeyChanged(any(), any()) reset(observer) @@ -196,10 +196,10 @@ class BackupRestoreStorageManagerTest { assertThat(dataChangedCount).isEqualTo(1) } - keyedStorage.notifyChange("key", ChangeReason.DELETE) + keyedStorage.notifyChange("key", DataChangeReason.DELETE) verify(observer, never()).onChanged(any()) - verify(keyedObserver).onKeyChanged("key", ChangeReason.DELETE) - verify(anyKeyObserver).onKeyChanged("key", ChangeReason.DELETE) + verify(keyedObserver).onKeyChanged("key", DataChangeReason.DELETE) + verify(anyKeyObserver).onKeyChanged("key", DataChangeReason.DELETE) backupManager?.apply { assertThat(isDataChanged).isTrue() assertThat(dataChangedCount).isEqualTo(2) @@ -207,11 +207,11 @@ class BackupRestoreStorageManagerTest { reset(keyedObserver) // backup manager is not notified for restore event - fileStorage.notifyChange(ChangeReason.RESTORE) - keyedStorage.notifyChange("key", ChangeReason.RESTORE) - verify(observer).onChanged(ChangeReason.RESTORE) - verify(keyedObserver).onKeyChanged("key", ChangeReason.RESTORE) - verify(anyKeyObserver).onKeyChanged("key", ChangeReason.RESTORE) + fileStorage.notifyChange(DataChangeReason.RESTORE) + keyedStorage.notifyChange("key", DataChangeReason.RESTORE) + verify(observer).onChanged(DataChangeReason.RESTORE) + verify(keyedObserver).onKeyChanged("key", DataChangeReason.RESTORE) + verify(anyKeyObserver).onKeyChanged("key", DataChangeReason.RESTORE) backupManager?.apply { assertThat(isDataChanged).isTrue() assertThat(dataChangedCount).isEqualTo(2) diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt index 8638b2f20b52..0fdecb034f83 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/KeyedObserverTest.kt @@ -77,7 +77,7 @@ class KeyedObserverTest { var observer: KeyedObserver<Any?>? = KeyedObserver { _, _ -> counter.incrementAndGet() } keyedObservable.addObserver(observer!!, executor1) - keyedObservable.notifyChange(ChangeReason.UPDATE) + keyedObservable.notifyChange(DataChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) // trigger GC, the observer callback should not be invoked @@ -85,7 +85,7 @@ class KeyedObserverTest { System.gc() System.runFinalization() - keyedObservable.notifyChange(ChangeReason.UPDATE) + keyedObservable.notifyChange(DataChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) } @@ -95,7 +95,7 @@ class KeyedObserverTest { var keyObserver: KeyedObserver<Any>? = KeyedObserver { _, _ -> counter.incrementAndGet() } keyedObservable.addObserver(key1, keyObserver!!, executor1) - keyedObservable.notifyChange(key1, ChangeReason.UPDATE) + keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) // trigger GC, the observer callback should not be invoked @@ -103,7 +103,7 @@ class KeyedObserverTest { System.gc() System.runFinalization() - keyedObservable.notifyChange(key1, ChangeReason.UPDATE) + keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) } @@ -112,16 +112,16 @@ class KeyedObserverTest { keyedObservable.addObserver(observer1, executor1) keyedObservable.addObserver(observer2, executor2) - keyedObservable.notifyChange(ChangeReason.UPDATE) - verify(observer1).onKeyChanged(null, ChangeReason.UPDATE) - verify(observer2).onKeyChanged(null, ChangeReason.UPDATE) + keyedObservable.notifyChange(DataChangeReason.UPDATE) + verify(observer1).onKeyChanged(null, DataChangeReason.UPDATE) + verify(observer2).onKeyChanged(null, DataChangeReason.UPDATE) reset(observer1, observer2) keyedObservable.removeObserver(observer2) - keyedObservable.notifyChange(ChangeReason.DELETE) - verify(observer1).onKeyChanged(null, ChangeReason.DELETE) - verify(observer2, never()).onKeyChanged(null, ChangeReason.DELETE) + keyedObservable.notifyChange(DataChangeReason.DELETE) + verify(observer1).onKeyChanged(null, DataChangeReason.DELETE) + verify(observer2, never()).onKeyChanged(null, DataChangeReason.DELETE) } @Test @@ -129,16 +129,16 @@ class KeyedObserverTest { keyedObservable.addObserver(key1, keyedObserver1, executor1) keyedObservable.addObserver(key2, keyedObserver2, executor2) - keyedObservable.notifyChange(key1, ChangeReason.UPDATE) - verify(keyedObserver1).onKeyChanged(key1, ChangeReason.UPDATE) - verify(keyedObserver2, never()).onKeyChanged(key2, ChangeReason.UPDATE) + keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) + verify(keyedObserver1).onKeyChanged(key1, DataChangeReason.UPDATE) + verify(keyedObserver2, never()).onKeyChanged(key2, DataChangeReason.UPDATE) reset(keyedObserver1, keyedObserver2) keyedObservable.removeObserver(key1, keyedObserver1) - keyedObservable.notifyChange(key1, ChangeReason.DELETE) - verify(keyedObserver1, never()).onKeyChanged(key1, ChangeReason.DELETE) - verify(keyedObserver2, never()).onKeyChanged(key2, ChangeReason.DELETE) + keyedObservable.notifyChange(key1, DataChangeReason.DELETE) + verify(keyedObserver1, never()).onKeyChanged(key1, DataChangeReason.DELETE) + verify(keyedObserver2, never()).onKeyChanged(key2, DataChangeReason.DELETE) } @Test @@ -147,24 +147,24 @@ class KeyedObserverTest { keyedObservable.addObserver(key1, keyedObserver1, executor1) keyedObservable.addObserver(key2, keyedObserver2, executor1) - keyedObservable.notifyChange(ChangeReason.UPDATE) - verify(observer1).onKeyChanged(null, ChangeReason.UPDATE) - verify(keyedObserver1).onKeyChanged(key1, ChangeReason.UPDATE) - verify(keyedObserver2).onKeyChanged(key2, ChangeReason.UPDATE) + keyedObservable.notifyChange(DataChangeReason.UPDATE) + verify(observer1).onKeyChanged(null, DataChangeReason.UPDATE) + verify(keyedObserver1).onKeyChanged(key1, DataChangeReason.UPDATE) + verify(keyedObserver2).onKeyChanged(key2, DataChangeReason.UPDATE) reset(observer1, keyedObserver1, keyedObserver2) - keyedObservable.notifyChange(key1, ChangeReason.UPDATE) + keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) - verify(observer1).onKeyChanged(key1, ChangeReason.UPDATE) - verify(keyedObserver1).onKeyChanged(key1, ChangeReason.UPDATE) - verify(keyedObserver2, never()).onKeyChanged(key1, ChangeReason.UPDATE) + verify(observer1).onKeyChanged(key1, DataChangeReason.UPDATE) + verify(keyedObserver1).onKeyChanged(key1, DataChangeReason.UPDATE) + verify(keyedObserver2, never()).onKeyChanged(key1, DataChangeReason.UPDATE) reset(observer1, keyedObserver1, keyedObserver2) - keyedObservable.notifyChange(key2, ChangeReason.UPDATE) + keyedObservable.notifyChange(key2, DataChangeReason.UPDATE) - verify(observer1).onKeyChanged(key2, ChangeReason.UPDATE) - verify(keyedObserver1, never()).onKeyChanged(key2, ChangeReason.UPDATE) - verify(keyedObserver2).onKeyChanged(key2, ChangeReason.UPDATE) + verify(observer1).onKeyChanged(key2, DataChangeReason.UPDATE) + verify(keyedObserver1, never()).onKeyChanged(key2, DataChangeReason.UPDATE) + verify(keyedObserver2).onKeyChanged(key2, DataChangeReason.UPDATE) } @Test @@ -176,7 +176,7 @@ class KeyedObserverTest { keyedObservable.addObserver(observer, executor1) - keyedObservable.notifyChange(ChangeReason.UPDATE) + keyedObservable.notifyChange(DataChangeReason.UPDATE) keyedObservable.removeObserver(observer) } @@ -189,7 +189,7 @@ class KeyedObserverTest { keyedObservable.addObserver(key1, keyObserver, executor1) - keyedObservable.notifyChange(key1, ChangeReason.UPDATE) + keyedObservable.notifyChange(key1, DataChangeReason.UPDATE) keyedObservable.removeObserver(key1, keyObserver) } } diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt index 173c2b1d4b81..5d0303c06d41 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/ObserverTest.kt @@ -58,7 +58,7 @@ class ObserverTest { var observer: Observer? = Observer { counter.incrementAndGet() } observable.addObserver(observer!!, executor1) - observable.notifyChange(ChangeReason.UPDATE) + observable.notifyChange(DataChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) // trigger GC, the observer callback should not be invoked @@ -66,7 +66,7 @@ class ObserverTest { System.gc() System.runFinalization() - observable.notifyChange(ChangeReason.UPDATE) + observable.notifyChange(DataChangeReason.UPDATE) assertThat(counter.get()).isEqualTo(1) } @@ -75,17 +75,17 @@ class ObserverTest { observable.addObserver(observer1, executor1) observable.addObserver(observer2, executor2) - observable.notifyChange(ChangeReason.DELETE) + observable.notifyChange(DataChangeReason.DELETE) - verify(observer1).onChanged(ChangeReason.DELETE) - verify(observer2).onChanged(ChangeReason.DELETE) + verify(observer1).onChanged(DataChangeReason.DELETE) + verify(observer2).onChanged(DataChangeReason.DELETE) reset(observer1, observer2) observable.removeObserver(observer2) - observable.notifyChange(ChangeReason.UPDATE) - verify(observer1).onChanged(ChangeReason.UPDATE) - verify(observer2, never()).onChanged(ChangeReason.UPDATE) + observable.notifyChange(DataChangeReason.UPDATE) + verify(observer1).onChanged(DataChangeReason.UPDATE) + verify(observer2, never()).onChanged(DataChangeReason.UPDATE) } @Test @@ -93,7 +93,7 @@ class ObserverTest { // ConcurrentModificationException is raised if it is not implemented correctly val observer = Observer { observable.addObserver(observer1, executor1) } observable.addObserver(observer, executor1) - observable.notifyChange(ChangeReason.UPDATE) + observable.notifyChange(DataChangeReason.UPDATE) observable.removeObserver(observer) } } diff --git a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt index fec7d758b893..a135d77e6d25 100644 --- a/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt +++ b/packages/SettingsLib/DataStore/tests/src/com/android/settingslib/datastore/SharedPreferencesStorageTest.kt @@ -80,13 +80,13 @@ class SharedPreferencesStorageTest { storage.addObserver("key", keyedObserver, executor) storage.sharedPreferences.edit().putString("key", "string").applySync() - verify(observer).onKeyChanged("key", ChangeReason.UPDATE) - verify(keyedObserver).onKeyChanged("key", ChangeReason.UPDATE) + verify(observer).onKeyChanged("key", DataChangeReason.UPDATE) + verify(keyedObserver).onKeyChanged("key", DataChangeReason.UPDATE) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { storage.sharedPreferences.edit().clear().applySync() - verify(observer).onKeyChanged(null, ChangeReason.DELETE) - verify(keyedObserver).onKeyChanged("key", ChangeReason.DELETE) + verify(observer).onKeyChanged(null, DataChangeReason.DELETE) + verify(keyedObserver).onKeyChanged("key", DataChangeReason.DELETE) } } diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp index c3a91a20c339..cd8f584953aa 100644 --- a/packages/SettingsLib/IllustrationPreference/Android.bp +++ b/packages/SettingsLib/IllustrationPreference/Android.bp @@ -47,6 +47,7 @@ java_aconfig_library { 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 bc3488fc31fb..cf645f73beb3 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 @@ public class LottieColorUtils { 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 @@ public class LottieColorUtils { ".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 @@ public class LottieColorUtils { 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/framework/compose/LifecycleEffect.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LifecycleEffect.kt index e91fa65401a4..e9f9689cb319 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LifecycleEffect.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/LifecycleEffect.kt @@ -18,9 +18,9 @@ package com.android.settingslib.spa.framework.compose import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.compose.LocalLifecycleOwner @Composable fun LifecycleEffect( diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt index 3991f26e1b0c..0b1c92d78a57 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/OnBackEffect.kt @@ -24,7 +24,7 @@ import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.compose.LocalLifecycleOwner /** * An effect for detecting presses of the system back button, and the back event will not be diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt index ee24a09d4395..007f47bd3c82 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/slice/presenter/Demo.kt @@ -21,8 +21,8 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.viewinterop.AndroidView +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.slice.widget.SliceLiveData import androidx.slice.widget.SliceView diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt index de080e3d8ef4..022ddedd1062 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialog.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.window.DialogProperties data class AlertDialogButton( val text: String, + val enabled: Boolean = true, val onClick: () -> Unit = {}, ) @@ -114,6 +115,7 @@ private fun AlertDialogPresenter.Button(button: AlertDialogButton) { close() button.onClick() }, + enabled = button.enabled, ) { Text(button.text) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt index b471e50be275..bdbe62c07425 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/DropdownTextBox.kt @@ -22,6 +22,7 @@ import androidx.compose.foundation.layout.width import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExposedDropdownMenuBox import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.MenuAnchorType import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -65,7 +66,7 @@ internal fun DropdownTextBox( OutlinedTextField( // The `menuAnchor` modifier must be passed to the text field for correctness. modifier = Modifier - .menuAnchor() + .menuAnchor(MenuAnchorType.PrimaryNotEditable) .fillMaxWidth(), value = text, onValueChange = { }, 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 a6cc3a9a6dd2..ea4480fbe235 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 @@ fun Lottie( 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 @@ object LottieColorUtils { ".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/framework/compose/LifecycleEffectTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/LifecycleEffectTest.kt index fe7baff43101..8b0efff591f2 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/LifecycleEffectTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/compose/LifecycleEffectTest.kt @@ -18,9 +18,9 @@ package com.android.settingslib.spa.framework.compose import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect -import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.test.junit4.createComposeRule import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.testing.TestLifecycleOwner import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt index 9468f95a094e..20ea397d7222 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/dialog/SettingsAlertDialogTest.kt @@ -20,6 +20,8 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertIsNotEnabled import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.performClick import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -67,7 +69,18 @@ class SettingsAlertDialogTest { rememberAlertDialogPresenter(confirmButton = AlertDialogButton(CONFIRM_TEXT)) } - composeTestRule.onDialogText(CONFIRM_TEXT).assertIsDisplayed() + composeTestRule.onDialogText(CONFIRM_TEXT).assertIsDisplayed().assertIsEnabled() + } + + @Test + fun confirmButton_disabled() { + setAndOpenDialog { + rememberAlertDialogPresenter( + confirmButton = AlertDialogButton(text = CONFIRM_TEXT, enabled = false) + ) + } + + composeTestRule.onDialogText(CONFIRM_TEXT).assertIsDisplayed().assertIsNotEnabled() } @Test @@ -90,7 +103,18 @@ class SettingsAlertDialogTest { rememberAlertDialogPresenter(dismissButton = AlertDialogButton(DISMISS_TEXT)) } - composeTestRule.onDialogText(DISMISS_TEXT).assertIsDisplayed() + composeTestRule.onDialogText(DISMISS_TEXT).assertIsDisplayed().assertIsEnabled() + } + + @Test + fun dismissButton_disabled() { + setAndOpenDialog { + rememberAlertDialogPresenter( + dismissButton = AlertDialogButton(text = DISMISS_TEXT, enabled = false) + ) + } + + composeTestRule.onDialogText(DISMISS_TEXT).assertIsDisplayed().assertIsNotEnabled() } @Test 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 71072a5d58c5..d91c7e66857d 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 @@ class CopyableBodyTest { @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 @@ class CopyableBodyTest { } 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/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt index 977615b55a6a..f95cfc3191c8 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt @@ -30,22 +30,24 @@ import com.android.settingslib.spaprivileged.model.enterprise.rememberRestricted @Composable fun MoreOptionsScope.RestrictedMenuItem( text: String, + enabled: Boolean = true, restrictions: Restrictions, onClick: () -> Unit, ) { - RestrictedMenuItemImpl(text, restrictions, onClick, ::RestrictionsProviderImpl) + RestrictedMenuItemImpl(text, enabled, restrictions, onClick, ::RestrictionsProviderImpl) } @VisibleForTesting @Composable internal fun MoreOptionsScope.RestrictedMenuItemImpl( text: String, + enabled: Boolean = true, restrictions: Restrictions, onClick: () -> Unit, restrictionsProviderFactory: RestrictionsProviderFactory, ) { val restrictedMode = restrictionsProviderFactory.rememberRestrictedMode(restrictions).value - MenuItem(text = text, enabled = restrictedMode !== BaseUserRestricted) { + MenuItem(text = text, enabled = enabled && restrictedMode !== BaseUserRestricted) { when (restrictedMode) { is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent() is BlockedByEcm -> restrictedMode.showRestrictedSettingsDetails() diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt index 556adc750763..4068bceb1475 100644 --- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt +++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItemTest.kt @@ -49,6 +49,15 @@ class RestrictedMenuItemTest { private var menuItemOnClickIsCalled = false @Test + fun whenDisabled() { + val restrictions = Restrictions(userId = USER_ID, keys = emptyList()) + + setContent(restrictions, enabled = false) + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed().assertIsNotEnabled() + } + + @Test fun whenRestrictionsKeysIsEmpty_enabled() { val restrictions = Restrictions(userId = USER_ID, keys = emptyList()) @@ -153,13 +162,14 @@ class RestrictedMenuItemTest { assertThat(menuItemOnClickIsCalled).isFalse() } - private fun setContent(restrictions: Restrictions) { + private fun setContent(restrictions: Restrictions, enabled: Boolean = true) { val fakeMoreOptionsScope = object : MoreOptionsScope() { override fun dismiss() {} } composeTestRule.setContent { fakeMoreOptionsScope.RestrictedMenuItemImpl( text = TEXT, + enabled = enabled, restrictions = restrictions, onClick = { menuItemOnClickIsCalled = true }, restrictionsProviderFactory = { _, _ -> fakeRestrictionsProvider }, diff --git a/packages/SettingsLib/res/values/dimens.xml b/packages/SettingsLib/res/values/dimens.xml index ab049042b5f9..470cdeea149b 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 869fb7f4043c..70811951c9b7 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 @@ class LocalMediaRepositoryImpl( 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 @@ class LocalMediaRepositoryImpl( .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 f83928dff98e..03c2a83519d8 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 @@ public class GlobalSettingsValidators { 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 473955f9b679..4ec170dda6f3 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -619,6 +619,7 @@ public class SettingsBackupTest { 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 ca1e4c10d339..e4898daf3cbf 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 android.support.test.uiautomator.UiDevice; 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 e940674a1cf5..07a00fb7e8e3 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -78,15 +78,44 @@ filegroup { visibility: ["//visibility:private"], } +// Tests where robolectric failed at runtime. (go/multivalent-tests) filegroup { name: "SystemUI-tests-broken-robofiles-run", srcs: [ - "tests/src/**/systemui/util/LifecycleFragmentTest.java", - "tests/src/**/systemui/util/TestableAlertDialogTest.kt", - "tests/src/**/systemui/util/kotlin/PairwiseFlowTest", - "tests/src/**/systemui/util/sensors/AsyncManagerTest.java", - "tests/src/**/systemui/util/sensors/ThresholdSensorImplTest.java", - "tests/src/**/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java", + "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/CustomizationProviderTest.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/navigationbar/NavigationBarButtonTest.java", + "tests/src/**/systemui/people/PeopleProviderTest.java", + "tests/src/**/systemui/people/PeopleSpaceUtilsTest.java", + "tests/src/**/systemui/people/widget/PeopleSpaceWidgetManagerTest.java", + "tests/src/**/systemui/people/PeopleTileViewHelperTest.java", + "tests/src/**/systemui/power/data/repository/PowerRepositoryImplTest.kt", + "tests/src/**/systemui/privacy/PrivacyConfigFlagsTest.kt", + "tests/src/**/systemui/privacy/PrivacyDialogV2Test.kt", + "tests/src/**/systemui/qs/external/TileRequestDialogEventLoggerTest.kt", + "tests/src/**/systemui/qs/AutoAddTrackerTest.kt", + "tests/src/**/systemui/qs/external/TileRequestDialogEventLoggerTest.kt", + "tests/src/**/systemui/qs/tiles/DndTileTest.kt", + "tests/src/**/systemui/qs/tiles/DreamTileTest.java", + "tests/src/**/systemui/qs/FgsManagerControllerTest.java", + "tests/src/**/systemui/qs/QSPanelTest.kt", + "tests/src/**/systemui/reardisplay/RearDisplayDialogControllerTest.java", "tests/src/**/systemui/statusbar/KeyboardShortcutListSearchTest.java", "tests/src/**/systemui/statusbar/KeyboardShortcutsTest.java", "tests/src/**/systemui/statusbar/KeyguardIndicationControllerWithCoroutinesTest.kt", @@ -133,6 +162,17 @@ filegroup { "tests/src/**/systemui/statusbar/policy/RemoteInputViewTest.java", "tests/src/**/systemui/statusbar/policy/SmartReplyViewTest.java", "tests/src/**/systemui/statusbar/StatusBarStateControllerImplTest.kt", + "tests/src/**/systemui/theme/ThemeOverlayApplierTest.java", + "tests/src/**/systemui/touch/TouchInsetManagerTest.java", + "tests/src/**/systemui/util/LifecycleFragmentTest.java", + "tests/src/**/systemui/util/TestableAlertDialogTest.kt", + "tests/src/**/systemui/util/kotlin/PairwiseFlowTest", + "tests/src/**/systemui/util/sensors/AsyncManagerTest.java", + "tests/src/**/systemui/util/sensors/ThresholdSensorImplTest.java", + "tests/src/**/systemui/util/wakelock/KeepAwakeAnimationListenerTest.java", + "tests/src/**/systemui/volume/VolumeDialogImplTest.java", + "tests/src/**/systemui/wallet/controller/QuickAccessWalletControllerTest.java", + "tests/src/**/systemui/wallet/ui/WalletScreenControllerTest.java", ], } diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index 7ce8f98a4cbb..2cb297ad1359 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -33,6 +33,13 @@ flag { } flag { + name: "notification_row_content_binder_refactor" + namespace: "systemui" + description: "Convert the NotificationContentInflater to Kotlin and restructure it to support modern views" + bug: "343942780" +} + +flag { name: "notification_minimalism_prototype" namespace: "systemui" description: "Prototype of notification minimalism; the new 'Intermediate' lockscreen customization proposal." @@ -177,16 +184,9 @@ flag { } 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" } @@ -414,6 +414,13 @@ flag { } flag { + name: "clock_reactive_variants" + namespace: "systemui" + description: "Add reactive variant fonts to some clocks" + bug: "343495953" +} + +flag { name: "fast_unlock_transition" namespace: "systemui" description: "Faster wallpaper unlock transition" @@ -743,16 +750,6 @@ flag { } 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 +994,13 @@ flag { } 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 +1015,13 @@ flag { } 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/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxConfig.kt new file mode 100644 index 000000000000..72f0e86f9dce --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxConfig.kt @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.surfaceeffects.glowboxeffect + +/** Parameters used to play [GlowBoxEffect]. */ +data class GlowBoxConfig( + /** Start center position X in px. */ + val startCenterX: Float, + /** Start center position Y in px. */ + val startCenterY: Float, + /** End center position X in px. */ + val endCenterX: Float, + /** End center position Y in px. */ + val endCenterY: Float, + /** Width of the box in px. */ + val width: Float, + /** Height of the box in px. */ + val height: Float, + /** Color of the box in ARGB, Apply alpha value if needed. */ + val color: Int, + /** Amount of blur (or glow) of the box. */ + val blurAmount: Float, + /** + * Duration of the animation. Note that the full duration of the animation is + * [duration] + [easeInDuration] + [easeOutDuration]. + */ + val duration: Long, + /** Ease in duration of the animation. */ + val easeInDuration: Long, + /** Ease out duration of the animation. */ + val easeOutDuration: Long, +) diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffect.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffect.kt new file mode 100644 index 000000000000..5e590c1ca010 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffect.kt @@ -0,0 +1,185 @@ +/* + * 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.surfaceeffects.glowboxeffect + +import android.animation.ValueAnimator +import android.graphics.Paint +import androidx.annotation.VisibleForTesting +import androidx.core.animation.doOnEnd +import com.android.systemui.surfaceeffects.PaintDrawCallback +import com.android.systemui.surfaceeffects.utils.MathUtils.lerp + +/** Glow box effect where the box moves from start to end positions defined in the [config]. */ +class GlowBoxEffect( + private var config: GlowBoxConfig, + private val paintDrawCallback: PaintDrawCallback, + private val stateChangedCallback: AnimationStateChangedCallback? = null +) { + private val glowBoxShader = + GlowBoxShader().apply { + setSize(config.width, config.height) + setCenter(config.startCenterX, config.startCenterY) + setBlur(config.blurAmount) + setColor(config.color) + } + private var animator: ValueAnimator? = null + @VisibleForTesting var state: AnimationState = AnimationState.NOT_PLAYING + private val paint = Paint().apply { shader = glowBoxShader } + + fun updateConfig(newConfig: GlowBoxConfig) { + this.config = newConfig + + with(glowBoxShader) { + setSize(config.width, config.height) + setCenter(config.startCenterX, config.startCenterY) + setBlur(config.blurAmount) + setColor(config.color) + } + } + + fun play() { + if (state != AnimationState.NOT_PLAYING) { + return + } + + playEaseIn() + } + + /** Finishes the animation with ease out. */ + fun finish(force: Boolean = false) { + // If it's playing ease out, cancel immediately. + if (force && state == AnimationState.EASE_OUT) { + animator?.cancel() + return + } + + // If it's playing either ease in or main, fast-forward to ease out. + if (state == AnimationState.EASE_IN || state == AnimationState.MAIN) { + animator?.pause() + playEaseOut() + } + + // At this point, animation state should be ease out. Cancel it if force is true. + if (force) { + animator?.cancel() + } + } + + private fun playEaseIn() { + if (state == AnimationState.EASE_IN) { + return + } + state = AnimationState.EASE_IN + stateChangedCallback?.onStart() + + animator = + ValueAnimator.ofFloat(0f, 1f).apply { + duration = config.easeInDuration + addUpdateListener { + val progress = it.animatedValue as Float + glowBoxShader.setCenter( + lerp(config.startCenterX, config.endCenterX, progress), + lerp(config.startCenterY, config.endCenterY, progress) + ) + + draw() + } + + doOnEnd { + animator = null + playMain() + } + + start() + } + } + + private fun playMain() { + if (state == AnimationState.MAIN) { + return + } + state = AnimationState.MAIN + + animator = + ValueAnimator.ofFloat(0f, 1f).apply { + duration = config.duration + addUpdateListener { draw() } + + doOnEnd { + animator = null + playEaseOut() + } + + start() + } + } + + private fun playEaseOut() { + if (state == AnimationState.EASE_OUT) return + state = AnimationState.EASE_OUT + + animator = + ValueAnimator.ofFloat(0f, 1f).apply { + duration = config.easeOutDuration + addUpdateListener { + val progress = it.animatedValue as Float + glowBoxShader.setCenter( + lerp(config.endCenterX, config.startCenterX, progress), + lerp(config.endCenterY, config.startCenterY, progress) + ) + + draw() + } + + doOnEnd { + animator = null + state = AnimationState.NOT_PLAYING + stateChangedCallback?.onEnd() + } + + start() + } + } + + private fun draw() { + paintDrawCallback.onDraw(paint) + } + + /** + * The animation state of the effect. The animation state transitions as follows: [EASE_IN] -> + * [MAIN] -> [EASE_OUT] -> [NOT_PLAYING]. + */ + enum class AnimationState { + EASE_IN, + MAIN, + EASE_OUT, + NOT_PLAYING, + } + + interface AnimationStateChangedCallback { + /** + * Triggered when the animation starts, specifically when the states goes from + * [AnimationState.NOT_PLAYING] to [AnimationState.EASE_IN]. + */ + fun onStart() + /** + * Triggered when the animation ends, specifically when the states goes from + * [AnimationState.EASE_OUT] to [AnimationState.NOT_PLAYING]. + */ + fun onEnd() + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxShader.kt new file mode 100644 index 000000000000..36934086cc23 --- /dev/null +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxShader.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.systemui.surfaceeffects.glowboxeffect + +import android.graphics.RuntimeShader +import com.android.systemui.surfaceeffects.shaderutil.SdfShaderLibrary + +/** Soft box shader. */ +class GlowBoxShader : RuntimeShader(GLOW_SHADER) { + // language=AGSL + private companion object { + private const val SHADER = + """ + uniform half2 in_center; + uniform half2 in_size; + uniform half in_blur; + layout(color) uniform half4 in_color; + + float4 main(float2 fragcoord) { + half glow = soften(sdBox(fragcoord - in_center, in_size), in_blur); + return in_color * (1. - glow); + } + """ + + private const val GLOW_SHADER = + SdfShaderLibrary.BOX_SDF + SdfShaderLibrary.SHADER_SDF_OPERATION_LIB + SHADER + } + + fun setCenter(x: Float, y: Float) { + setFloatUniform("in_center", x, y) + } + + fun setSize(width: Float, height: Float) { + setFloatUniform("in_size", width, height) + } + + fun setBlur(blurAmount: Float) { + setFloatUniform("in_blur", blurAmount) + } + + fun setColor(color: Int) { + setColorUniform("in_color", color) + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt index 78898932249b..4efab58347dd 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/SdfShaderLibrary.kt @@ -35,17 +35,26 @@ class SdfShaderLibrary { } """ + const val BOX_SDF = + """ + float sdBox(vec2 p, vec2 size) { + size = size * 0.5; + vec2 d = abs(p) - size; + return length(max(d, 0.)) + min(max(d.x, d.y), 0.) / size.y; + } + """ + const val ROUNDED_BOX_SDF = """ float sdRoundedBox(vec2 p, vec2 size, float cornerRadius) { size *= 0.5; cornerRadius *= 0.5; - vec2 d = abs(p)-size+cornerRadius; + vec2 d = abs(p) - size + cornerRadius; float outside = length(max(d, 0.0)); float inside = min(max(d.x, d.y), 0.0); - return (outside+inside-cornerRadius)/size.y; + return (outside + inside - cornerRadius) / size.y; } float roundedBoxRing(vec2 p, vec2 size, float cornerRadius, diff --git a/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt index 608f301da85d..7ed3b87f684e 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/data/model/LinearBrightness.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/utils/MathUtils.kt @@ -14,17 +14,11 @@ * limitations under the License. */ -package com.android.systemui.brightness.data.model +package com.android.systemui.surfaceeffects.utils -@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 - } +/** Copied from android.utils.MathUtils */ +object MathUtils { + fun lerp(start: Float, stop: Float, amount: Float): Float { + return start + (stop - start) * amount } } 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 dff8753fd880..000000000000 --- 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 feb1f5b17bef..c32938497147 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 @@ -1,9 +1,16 @@ package com.android.systemui.communal.ui.compose +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.tween import androidx.compose.foundation.background +import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -13,14 +20,24 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.composed +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.unit.IntSize 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 @@ -34,7 +51,10 @@ import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.observableTransitionState import com.android.compose.animation.scene.transitions +import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.Flags +import com.android.systemui.Flags.glanceableHubFullscreenSwipe +import com.android.systemui.communal.shared.model.CommunalBackgroundType 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 @@ -99,6 +119,10 @@ fun CommunalContainer( val touchesAllowed by viewModel.touchesAllowed.collectAsStateWithLifecycle(initialValue = false) val showGestureIndicator by viewModel.showGestureIndicator.collectAsStateWithLifecycle(initialValue = false) + val backgroundType by + viewModel.communalBackground.collectAsStateWithLifecycle( + initialValue = CommunalBackgroundType.DEFAULT + ) val state: MutableSceneTransitionLayoutState = remember { MutableSceneTransitionLayoutState( initialScene = currentSceneKey, @@ -108,6 +132,8 @@ fun CommunalContainer( ) } + val detector = remember { CommunalSwipeDetector() } + DisposableEffect(state) { val dataSource = SceneTransitionLayoutDataSource(state, coroutineScope) dataSourceDelegator.setDelegate(dataSource) @@ -121,13 +147,25 @@ fun CommunalContainer( 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, @@ -157,7 +195,7 @@ fun CommunalContainer( userActions = mapOf(Swipe(SwipeDirection.Right, fromSource = Edge.Left) to CommunalScenes.Blank) ) { - CommunalScene(colors, content) + CommunalScene(backgroundType, colors, content) } } @@ -169,17 +207,87 @@ fun CommunalContainer( /** Scene containing the glanceable hub UI. */ @Composable private fun SceneScope.CommunalScene( + backgroundType: CommunalBackgroundType, colors: CommunalColors, content: CommunalContent, modifier: Modifier = Modifier, ) { + Box(modifier = Modifier.element(Communal.Elements.Scrim).fillMaxSize()) { + when (backgroundType) { + CommunalBackgroundType.DEFAULT -> DefaultBackground(colors = colors) + CommunalBackgroundType.STATIC_GRADIENT -> StaticLinearGradient() + CommunalBackgroundType.ANIMATED -> AnimatedLinearGradient() + } + } + with(content) { Content(modifier = modifier) } +} + +/** Default background of the hub, a single color */ +@Composable +private fun BoxScope.DefaultBackground( + colors: CommunalColors, +) { val backgroundColor by colors.backgroundColor.collectAsStateWithLifecycle() + Box( + modifier = Modifier.matchParentSize().background(Color(backgroundColor.toArgb())), + ) +} +/** Experimental hub background, static linear gradient */ +@Composable +private fun BoxScope.StaticLinearGradient() { + val colors = LocalAndroidColorScheme.current Box( - modifier = - Modifier.element(Communal.Elements.Scrim) - .fillMaxSize() - .background(Color(backgroundColor.toArgb())), + Modifier.matchParentSize() + .background( + Brush.linearGradient(colors = listOf(colors.primary, colors.primaryContainer)), + ) ) - with(content) { Content(modifier = modifier) } + BackgroundTopScrim() +} + +/** Experimental hub background, animated linear gradient */ +@Composable +private fun BoxScope.AnimatedLinearGradient() { + val colors = LocalAndroidColorScheme.current + Box( + Modifier.matchParentSize() + .animatedGradientBackground(colors = listOf(colors.primary, colors.primaryContainer)) + ) + BackgroundTopScrim() +} + +/** Scrim placed on top of the background in order to dim/bright colors */ +@Composable +private fun BoxScope.BackgroundTopScrim() { + val darkTheme = isSystemInDarkTheme() + val scrimOnTopColor = if (darkTheme) Color.Black else Color.White + Box(Modifier.matchParentSize().alpha(0.34f).background(scrimOnTopColor)) +} + +/** Modifier which sets the background of a composable to an animated gradient */ +@Composable +private fun Modifier.animatedGradientBackground(colors: List<Color>): Modifier = composed { + var size by remember { mutableStateOf(IntSize.Zero) } + val transition = rememberInfiniteTransition(label = "scrim background") + val startOffsetX by + transition.animateFloat( + initialValue = -size.width.toFloat(), + targetValue = size.width.toFloat(), + animationSpec = + infiniteRepeatable( + animation = tween(durationMillis = 5_000, easing = LinearEasing), + repeatMode = RepeatMode.Reverse, + ), + label = "scrim start offset" + ) + background( + brush = + Brush.linearGradient( + colors = colors, + start = Offset(startOffsetX, 0f), + end = Offset(startOffsetX + size.width.toFloat(), size.height.toFloat()), + ) + ) + .onGloballyPositioned { size = it.size } } 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 9dd3d39cb040..1f7f07bb072d 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.AnimatedVisibility 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 @@ private fun BoxScope.CommunalHubLazyGrid( 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 c26259f3287c..27a834bdeb2d 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 @@ fun SceneScope.HeadsUpNotificationSpace( " size=${coordinates.size}" + " bounds=$boundsInWindow" } - viewModel.onHeadsUpTopChanged(boundsInWindow.top) + // Note: boundsInWindow doesn't scroll off the screen + stackScrollView.setHeadsUpTop(boundsInWindow.top) } ) { content {} @@ -169,6 +170,7 @@ fun SceneScope.NotificationScrollingStack( maxScrimTop: () -> Float, shouldPunchHoleBehindScrim: Boolean, shouldFillMaxSize: Boolean = true, + shouldReserveSpaceForNavBar: Boolean = true, shadeMode: ShadeMode, modifier: Modifier = Modifier, ) { @@ -352,7 +354,7 @@ fun SceneScope.NotificationScrollingStack( .fillMaxWidth() .notificationStackHeight( view = stackScrollView, - padding = navBarHeight.toInt() + padding = if (shouldReserveSpaceForNavBar) navBarHeight.toInt() else 0 ) .onSizeChanged { size -> stackHeight.intValue = size.height }, ) 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 ae53d56e331a..edb17273dba1 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 @@ package com.android.systemui.notifications.ui.composable 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 @@ constructor( OverlayShade( modifier = modifier, viewModel = overlayShadeViewModel, - horizontalArrangement = Arrangement.Start, + horizontalArrangement = Arrangement.End, lockscreenContent = lockscreenContent, ) { Column { @@ -94,8 +94,9 @@ constructor( maxScrimTop = { 0f }, shouldPunchHoleBehindScrim = false, shouldFillMaxSize = false, + shouldReserveSpaceForNavBar = false, shadeMode = ShadeMode.Dual, - modifier = Modifier.width(416.dp), + modifier = Modifier.fillMaxWidth(), ) } } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt index 4d946bff63ca..4bf90ec69f46 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt @@ -16,22 +16,44 @@ package com.android.systemui.qs.ui.composable +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +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.material3.Button import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult +import com.android.systemui.battery.BatteryMeterViewController +import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.composable.LockscreenContent +import com.android.systemui.qs.panels.ui.compose.EditMode +import com.android.systemui.qs.panels.ui.compose.TileGrid import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.scene.ui.composable.ComposableScene +import com.android.systemui.shade.ui.composable.ExpandedShadeHeader import com.android.systemui.shade.ui.composable.OverlayShade -import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel +import com.android.systemui.shade.ui.viewmodel.ShadeHeaderViewModel +import com.android.systemui.statusbar.phone.ui.StatusBarIconController +import com.android.systemui.statusbar.phone.ui.TintedIconManager import dagger.Lazy import java.util.Optional import javax.inject.Inject @@ -41,9 +63,12 @@ import kotlinx.coroutines.flow.StateFlow class QuickSettingsShadeScene @Inject constructor( - viewModel: QuickSettingsShadeSceneViewModel, - private val overlayShadeViewModel: OverlayShadeViewModel, + private val viewModel: QuickSettingsShadeSceneViewModel, private val lockscreenContent: Lazy<Optional<LockscreenContent>>, + private val shadeHeaderViewModel: ShadeHeaderViewModel, + private val tintedIconManagerFactory: TintedIconManager.Factory, + private val batteryMeterViewControllerFactory: BatteryMeterViewController.Factory, + private val statusBarIconController: StatusBarIconController, ) : ComposableScene { override val key = Scenes.QuickSettingsShade @@ -56,21 +81,101 @@ constructor( modifier: Modifier, ) { OverlayShade( - viewModel = overlayShadeViewModel, - modifier = modifier, + viewModel = viewModel.overlayShadeViewModel, horizontalArrangement = Arrangement.End, lockscreenContent = lockscreenContent, + modifier = modifier, ) { - Text( - text = "Quick settings grid", - modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding) + Column { + ExpandedShadeHeader( + viewModel = shadeHeaderViewModel, + createTintedIconManager = tintedIconManagerFactory::create, + createBatteryMeterViewController = batteryMeterViewControllerFactory::create, + statusBarIconController = statusBarIconController, + modifier = Modifier.padding(QuickSettingsShade.Dimensions.Padding), + ) + + ShadeBody( + viewModel = viewModel, + ) + } + } + } +} + +@Composable +private fun ShadeBody( + viewModel: QuickSettingsShadeSceneViewModel, +) { + val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle() + + Box { + // The main Quick Settings grid layout. + AnimatedVisibility( + visible = !isEditing, + enter = QuickSettingsShade.Transitions.QuickSettingsLayoutEnter, + exit = QuickSettingsShade.Transitions.QuickSettingsLayoutExit, + ) { + QuickSettingsLayout( + viewModel = viewModel, ) } + + // The Quick Settings Editor layout. + AnimatedVisibility( + visible = isEditing, + enter = QuickSettingsShade.Transitions.QuickSettingsEditorEnter, + exit = QuickSettingsShade.Transitions.QuickSettingsEditorExit, + ) { + EditMode( + viewModel = viewModel.editModeViewModel, + modifier = Modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding) + ) + } + } +} + +@Composable +private fun QuickSettingsLayout( + viewModel: QuickSettingsShadeSceneViewModel, + modifier: Modifier = Modifier, +) { + Column( + verticalArrangement = Arrangement.spacedBy(QuickSettingsShade.Dimensions.Padding), + horizontalAlignment = Alignment.CenterHorizontally, + modifier = modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding), + ) { + BrightnessSliderContainer( + viewModel = viewModel.brightnessSliderViewModel, + modifier = + Modifier.fillMaxWidth() + .height(QuickSettingsShade.Dimensions.BrightnessSliderHeight), + ) + TileGrid( + viewModel = viewModel.tileGridViewModel, + modifier = + Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight), + ) + Button( + onClick = { viewModel.editModeViewModel.startEditing() }, + ) { + Text("Edit mode") + } } } object QuickSettingsShade { + object Dimensions { val Padding = 16.dp + val BrightnessSliderHeight = 64.dp + val GridMaxHeight = 400.dp + } + + object Transitions { + val QuickSettingsLayoutEnter: EnterTransition = fadeIn(tween(500)) + val QuickSettingsLayoutExit: ExitTransition = fadeOut(tween(500)) + val QuickSettingsEditorEnter: EnterTransition = fadeIn(tween(500)) + val QuickSettingsEditorExit: ExitTransition = fadeOut(tween(500)) } } 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 975829ab3760..efda4cd3638e 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 @@ import kotlinx.coroutines.flow.StateFlow 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 @@ constructor( 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 f5a0ef2adde7..10c403055c8e 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.lockscreenToQuickSet 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 @@ val SceneContainerTransitions = transitions { 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 a6b268d59bf4..6b3b7602050d 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 @@ fun TransitionBuilder.toNotificationsShadeTransition( } } - 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 2baaecf47ec5..ec2f14f34dba 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 @@ fun TransitionBuilder.toQuickSettingsShadeTransition( } } - 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 34cc6769eb40..6924d432453c 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.runtime.getValue 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 @@ fun SceneScope.OverlayShade( 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 @@ fun SceneScope.OverlayShade( 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 @@ private fun Modifier.panelSize(): Modifier { ) } +@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 @@ object OverlayShade { val PanelCornerRadius = 46.dp val PanelWidthMedium = 390.dp val PanelWidthLarge = 474.dp + val OverscrollLimit = 32.dp } 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 da29d5810974..48af8cdd533b 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 @@ interface SpatialAudioModule { 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 000000000000..2d89b5cac3ee --- /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 271eb9601dbd..fbf91b702fb9 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 @@ fun VolumeSlider( 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 ac5004e16a3b..580aba586ee1 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 @@ fun VolumePanelComposeScope.HorizontalVolumePanelContent( 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 9ea20b9da4b6..6349c1406a12 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 @@ fun VolumePanelComposeScope.VerticalVolumePanelContent( 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/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt index 5d1a7c5c840f..7fd3a176acaa 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt @@ -27,11 +27,12 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.lerp +import androidx.compose.ui.graphics.colorspace.ColorSpaces import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.lerp +import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastCoerceIn -import androidx.compose.ui.util.lerp +import androidx.compose.ui.util.fastLastOrNull +import kotlin.math.roundToInt /** * A [State] whose [value] is animated. @@ -74,7 +75,7 @@ fun SceneScope.animateSceneIntAsState( key: ValueKey, canOverflow: Boolean = true, ): AnimatedState<Int> { - return animateSceneValueAsState(value, key, ::lerp, canOverflow) + return animateSceneValueAsState(value, key, SharedIntType, canOverflow) } /** @@ -88,7 +89,19 @@ fun ElementScope<*>.animateElementIntAsState( key: ValueKey, canOverflow: Boolean = true, ): AnimatedState<Int> { - return animateElementValueAsState(value, key, ::lerp, canOverflow) + return animateElementValueAsState(value, key, SharedIntType, canOverflow) +} + +private object SharedIntType : SharedValueType<Int, Int> { + override val unspecifiedValue: Int = Int.MIN_VALUE + override val zeroDeltaValue: Int = 0 + + override fun lerp(a: Int, b: Int, progress: Float): Int = + androidx.compose.ui.util.lerp(a, b, progress) + + override fun diff(a: Int, b: Int): Int = a - b + + override fun addWeighted(a: Int, b: Int, bWeight: Float): Int = (a + b * bWeight).roundToInt() } /** @@ -102,7 +115,7 @@ fun SceneScope.animateSceneFloatAsState( key: ValueKey, canOverflow: Boolean = true, ): AnimatedState<Float> { - return animateSceneValueAsState(value, key, ::lerp, canOverflow) + return animateSceneValueAsState(value, key, SharedFloatType, canOverflow) } /** @@ -116,7 +129,19 @@ fun ElementScope<*>.animateElementFloatAsState( key: ValueKey, canOverflow: Boolean = true, ): AnimatedState<Float> { - return animateElementValueAsState(value, key, ::lerp, canOverflow) + return animateElementValueAsState(value, key, SharedFloatType, canOverflow) +} + +private object SharedFloatType : SharedValueType<Float, Float> { + override val unspecifiedValue: Float = Float.MIN_VALUE + override val zeroDeltaValue: Float = 0f + + override fun lerp(a: Float, b: Float, progress: Float): Float = + androidx.compose.ui.util.lerp(a, b, progress) + + override fun diff(a: Float, b: Float): Float = a - b + + override fun addWeighted(a: Float, b: Float, bWeight: Float): Float = a + b * bWeight } /** @@ -130,7 +155,7 @@ fun SceneScope.animateSceneDpAsState( key: ValueKey, canOverflow: Boolean = true, ): AnimatedState<Dp> { - return animateSceneValueAsState(value, key, ::lerp, canOverflow) + return animateSceneValueAsState(value, key, SharedDpType, canOverflow) } /** @@ -144,7 +169,20 @@ fun ElementScope<*>.animateElementDpAsState( key: ValueKey, canOverflow: Boolean = true, ): AnimatedState<Dp> { - return animateElementValueAsState(value, key, ::lerp, canOverflow) + return animateElementValueAsState(value, key, SharedDpType, canOverflow) +} + +private object SharedDpType : SharedValueType<Dp, Dp> { + override val unspecifiedValue: Dp = Dp.Unspecified + override val zeroDeltaValue: Dp = 0.dp + + override fun lerp(a: Dp, b: Dp, progress: Float): Dp { + return androidx.compose.ui.unit.lerp(a, b, progress) + } + + override fun diff(a: Dp, b: Dp): Dp = a - b + + override fun addWeighted(a: Dp, b: Dp, bWeight: Float): Dp = a + b * bWeight } /** @@ -157,7 +195,7 @@ fun SceneScope.animateSceneColorAsState( value: Color, key: ValueKey, ): AnimatedState<Color> { - return animateSceneValueAsState(value, key, ::lerp, canOverflow = false) + return animateSceneValueAsState(value, key, SharedColorType, canOverflow = false) } /** @@ -170,9 +208,56 @@ fun ElementScope<*>.animateElementColorAsState( value: Color, key: ValueKey, ): AnimatedState<Color> { - return animateElementValueAsState(value, key, ::lerp, canOverflow = false) + return animateElementValueAsState(value, key, SharedColorType, canOverflow = false) +} + +private object SharedColorType : SharedValueType<Color, ColorDelta> { + override val unspecifiedValue: Color = Color.Unspecified + override val zeroDeltaValue: ColorDelta = ColorDelta(0f, 0f, 0f, 0f) + + override fun lerp(a: Color, b: Color, progress: Float): Color { + return androidx.compose.ui.graphics.lerp(a, b, progress) + } + + override fun diff(a: Color, b: Color): ColorDelta { + // Similar to lerp, we convert colors to the Oklab color space to perform operations on + // colors. + val aOklab = a.convert(ColorSpaces.Oklab) + val bOklab = b.convert(ColorSpaces.Oklab) + return ColorDelta( + red = aOklab.red - bOklab.red, + green = aOklab.green - bOklab.green, + blue = aOklab.blue - bOklab.blue, + alpha = aOklab.alpha - bOklab.alpha, + ) + } + + override fun addWeighted(a: Color, b: ColorDelta, bWeight: Float): Color { + val aOklab = a.convert(ColorSpaces.Oklab) + return Color( + red = aOklab.red + b.red * bWeight, + green = aOklab.green + b.green * bWeight, + blue = aOklab.blue + b.blue * bWeight, + alpha = aOklab.alpha + b.alpha * bWeight, + colorSpace = ColorSpaces.Oklab, + ) + .convert(aOklab.colorSpace) + } } +/** + * Represents the diff between two colors in the same color space. + * + * Note: This class is necessary because Color() checks the bounds of its values and UncheckedColor + * is internal. + */ +private class ColorDelta( + val red: Float, + val green: Float, + val blue: Float, + val alpha: Float, +) + @Composable internal fun <T> animateSharedValueAsState( layoutImpl: SceneTransitionLayoutImpl, @@ -180,23 +265,22 @@ internal fun <T> animateSharedValueAsState( element: ElementKey?, key: ValueKey, value: T, - lerp: (T, T, Float) -> T, + type: SharedValueType<T, *>, canOverflow: Boolean, ): AnimatedState<T> { DisposableEffect(layoutImpl, scene, element, key) { // Create the associated maps that hold the current value for each (element, scene) pair. val valueMap = layoutImpl.sharedValues.getOrPut(key) { mutableMapOf() } - val sceneToValueMap = - valueMap.getOrPut(element) { SnapshotStateMap<SceneKey, Any>() } - as SnapshotStateMap<SceneKey, T> - sceneToValueMap[scene] = value + val sharedValue = valueMap.getOrPut(element) { SharedValue(type) } as SharedValue<T, *> + val targetValues = sharedValue.targetValues + targetValues[scene] = value onDispose { // Remove the value associated to the current scene, and eventually remove the maps if // they are empty. - sceneToValueMap.remove(scene) + targetValues.remove(scene) - if (sceneToValueMap.isEmpty() && valueMap[element] === sceneToValueMap) { + if (targetValues.isEmpty() && valueMap[element] === sharedValue) { valueMap.remove(element) if (valueMap.isEmpty() && layoutImpl.sharedValues[key] === valueMap) { @@ -208,34 +292,25 @@ internal fun <T> animateSharedValueAsState( // Update the current value. Note that side effects run after disposable effects, so we know // that the associated maps were created at this point. - SideEffect { sceneToValueMap<T>(layoutImpl, key, element)[scene] = value } - - return remember(layoutImpl, scene, element, lerp, canOverflow) { - object : AnimatedState<T> { - override val value: T - get() = value(layoutImpl, scene, element, key, lerp, canOverflow) - - @Composable - override fun unsafeCompositionState(initialValue: T): State<T> { - val state = remember { mutableStateOf(initialValue) } + SideEffect { + if (value == type.unspecifiedValue) { + error("value is equal to $value, which is the undefined value for this type.") + } - val animatedState = this - LaunchedEffect(animatedState) { - snapshotFlow { animatedState.value }.collect { state.value = it } - } + sharedValue<T, Any>(layoutImpl, key, element).targetValues[scene] = value + } - return state - } - } + return remember(layoutImpl, scene, element, canOverflow) { + AnimatedStateImpl<T, Any>(layoutImpl, scene, element, key, canOverflow) } } -private fun <T> sceneToValueMap( +private fun <T, Delta> sharedValue( layoutImpl: SceneTransitionLayoutImpl, key: ValueKey, element: ElementKey? -): MutableMap<SceneKey, T> { - return layoutImpl.sharedValues[key]?.get(element)?.let { it as SnapshotStateMap<SceneKey, T> } +): SharedValue<T, Delta> { + return layoutImpl.sharedValues[key]?.get(element)?.let { it as SharedValue<T, Delta> } ?: error(valueReadTooEarlyMessage(key)) } @@ -244,62 +319,155 @@ private fun valueReadTooEarlyMessage(key: ValueKey) = "means that you are reading it during composition, which you should not do. See the " + "documentation of AnimatedState for more information." -private fun <T> value( - layoutImpl: SceneTransitionLayoutImpl, - scene: SceneKey, - element: ElementKey?, - key: ValueKey, - lerp: (T, T, Float) -> T, - canOverflow: Boolean, -): T { - return valueOrNull(layoutImpl, scene, element, key, lerp, canOverflow) - ?: error(valueReadTooEarlyMessage(key)) +internal class SharedValue<T, Delta>( + val type: SharedValueType<T, Delta>, +) { + /** The target value of this shared value for each scene. */ + val targetValues = SnapshotStateMap<SceneKey, T>() + + /** The last value of this shared value. */ + var lastValue: T = type.unspecifiedValue + + /** The value of this shared value before the last interruption (if any). */ + var valueBeforeInterruption: T = type.unspecifiedValue + + /** The delta value to add to this shared value to have smoother interruptions. */ + var valueInterruptionDelta = type.zeroDeltaValue + + /** The last transition that was used when the value of this shared state. */ + var lastTransition: TransitionState.Transition? = null } -private fun <T> valueOrNull( - layoutImpl: SceneTransitionLayoutImpl, - scene: SceneKey, - element: ElementKey?, - key: ValueKey, - lerp: (T, T, Float) -> T, - canOverflow: Boolean, -): T? { - val sceneToValueMap = sceneToValueMap<T>(layoutImpl, key, element) - fun sceneValue(scene: SceneKey): T? = sceneToValueMap[scene] - - return when (val transition = layoutImpl.state.transitionState) { - is TransitionState.Idle -> sceneValue(transition.currentScene) - is TransitionState.Transition -> { - // Note: no need to check for transition ready here given that all target values are - // defined during composition, we should already have the correct values to interpolate - // between here. - val fromValue = sceneValue(transition.fromScene) - val toValue = sceneValue(transition.toScene) - if (fromValue != null && toValue != null) { - if (fromValue == toValue) { - // Optimization: avoid reading progress if the values are the same, so we don't - // relayout/redraw for nothing. - fromValue - } else { - // In the case of bouncing, if the value remains constant during the overscroll, - // we should use the value of the scene we are bouncing around. - if (!canOverflow && transition is TransitionState.HasOverscrollProperties) { - val bouncingScene = transition.bouncingScene - if (bouncingScene != null) { - return sceneValue(bouncingScene) - } +private class AnimatedStateImpl<T, Delta>( + private val layoutImpl: SceneTransitionLayoutImpl, + private val scene: SceneKey, + private val element: ElementKey?, + private val key: ValueKey, + private val canOverflow: Boolean, +) : AnimatedState<T> { + override val value: T + get() = value() + + private fun value(): T { + val sharedValue = sharedValue<T, Delta>(layoutImpl, key, element) + val transition = transition(sharedValue) + val value: T = + valueOrNull(sharedValue, transition) + // TODO(b/311600838): Remove this. We should not have to fallback to the current + // scene value, but we have to because code of removed nodes can still run if they + // are placed with a graphics layer. + ?: sharedValue[scene] + ?: error(valueReadTooEarlyMessage(key)) + val interruptedValue = computeInterruptedValue(sharedValue, transition, value) + sharedValue.lastValue = interruptedValue + return interruptedValue + } + + private operator fun SharedValue<T, *>.get(scene: SceneKey): T? = targetValues[scene] + + private fun valueOrNull( + sharedValue: SharedValue<T, *>, + transition: TransitionState.Transition?, + ): T? { + if (transition == null) { + return sharedValue[layoutImpl.state.transitionState.currentScene] + } + + val fromValue = sharedValue[transition.fromScene] + val toValue = sharedValue[transition.toScene] + return if (fromValue != null && toValue != null) { + if (fromValue == toValue) { + // Optimization: avoid reading progress if the values are the same, so we don't + // relayout/redraw for nothing. + fromValue + } else { + // In the case of bouncing, if the value remains constant during the overscroll, we + // should use the value of the scene we are bouncing around. + if (!canOverflow && transition is TransitionState.HasOverscrollProperties) { + val bouncingScene = transition.bouncingScene + if (bouncingScene != null) { + return sharedValue[bouncingScene] } + } + + val progress = + if (canOverflow) transition.progress + else transition.progress.fastCoerceIn(0f, 1f) + sharedValue.type.lerp(fromValue, toValue, progress) + } + } else fromValue ?: toValue + } - val progress = - if (canOverflow) transition.progress - else transition.progress.fastCoerceIn(0f, 1f) - lerp(fromValue, toValue, progress) + private fun transition(sharedValue: SharedValue<T, Delta>): TransitionState.Transition? { + val targetValues = sharedValue.targetValues + val transition = + if (element != null) { + layoutImpl.elements[element]?.sceneStates?.let { sceneStates -> + layoutImpl.state.currentTransitions.fastLastOrNull { transition -> + transition.fromScene in sceneStates || transition.toScene in sceneStates + } + } + } else { + layoutImpl.state.currentTransitions.fastLastOrNull { transition -> + transition.fromScene in targetValues || transition.toScene in targetValues } - } else fromValue ?: toValue + } + + val previousTransition = sharedValue.lastTransition + sharedValue.lastTransition = transition + + if (transition != previousTransition && transition != null && previousTransition != null) { + // The previous transition was interrupted by another transition. + sharedValue.valueBeforeInterruption = sharedValue.lastValue + sharedValue.valueInterruptionDelta = sharedValue.type.zeroDeltaValue + } else if (transition == null && previousTransition != null) { + // The transition was just finished. + sharedValue.valueBeforeInterruption = sharedValue.type.unspecifiedValue + sharedValue.valueInterruptionDelta = sharedValue.type.zeroDeltaValue } + + return transition + } + + /** + * Compute what [value] should be if we take the + * [interruption progress][TransitionState.Transition.interruptionProgress] of [transition] into + * account. + */ + private fun computeInterruptedValue( + sharedValue: SharedValue<T, Delta>, + transition: TransitionState.Transition?, + value: T, + ): T { + val type = sharedValue.type + if (sharedValue.valueBeforeInterruption != type.unspecifiedValue) { + sharedValue.valueInterruptionDelta = + type.diff(sharedValue.valueBeforeInterruption, value) + sharedValue.valueBeforeInterruption = type.unspecifiedValue + } + + val delta = sharedValue.valueInterruptionDelta + return if (delta == type.zeroDeltaValue || transition == null) { + value + } else { + val interruptionProgress = transition.interruptionProgress(layoutImpl) + if (interruptionProgress == 0f) { + value + } else { + type.addWeighted(value, delta, interruptionProgress) + } + } + } + + @Composable + override fun unsafeCompositionState(initialValue: T): State<T> { + val state = remember { mutableStateOf(initialValue) } + + val animatedState = this + LaunchedEffect(animatedState) { + snapshotFlow { animatedState.value }.collect { state.value = it } + } + + return state } - // TODO(b/311600838): Remove this. We should not have to fallback to the current scene value, - // but we have to because code of removed nodes can still run if they are placed with a graphics - // layer. - ?: sceneValue(scene) } 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 000000000000..7be34cabfaf8 --- /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/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 1f812453915a..e9633c2f6603 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -148,9 +148,9 @@ internal class DraggableHandlerImpl( val swipes = computeSwipes(fromScene, startedPosition, pointersDown) val result = swipes.findUserActionResult(fromScene, overSlop, true) - // As we were unable to locate a valid target scene, the initial SwipeTransition - // cannot be defined. Consequently, a simple NoOp Controller will be returned. - ?: return NoOpDragController + // As we were unable to locate a valid target scene, the initial SwipeTransition + // cannot be defined. Consequently, a simple NoOp Controller will be returned. + ?: return NoOpDragController return updateDragController( swipes = swipes, @@ -521,6 +521,7 @@ private fun SwipeTransition( } return SwipeTransition( + layoutImpl = layoutImpl, layoutState = layoutState, coroutineScope = coroutineScope, key = result.transitionKey, @@ -534,6 +535,7 @@ private fun SwipeTransition( private fun SwipeTransition(old: SwipeTransition): SwipeTransition { return SwipeTransition( + layoutImpl = old.layoutImpl, layoutState = old.layoutState, coroutineScope = old.coroutineScope, key = old.key, @@ -550,6 +552,7 @@ private fun SwipeTransition(old: SwipeTransition): SwipeTransition { } private class SwipeTransition( + val layoutImpl: SceneTransitionLayoutImpl, val layoutState: BaseSceneTransitionLayoutState, val coroutineScope: CoroutineScope, val key: TransitionKey?, @@ -607,6 +610,12 @@ private class SwipeTransition( override val overscrollScope: OverscrollScope = object : OverscrollScope { + override val density: Float + get() = layoutImpl.density.density + + override val fontScale: Float + get() = layoutImpl.density.fontScale + override val absoluteDistance: Float get() = distance().absoluteValue } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt index 4b20acaee2bd..be005eac1b75 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt @@ -77,7 +77,7 @@ private abstract class BaseElementScope<ContentScope>( override fun <T> animateElementValueAsState( value: T, key: ValueKey, - lerp: (start: T, stop: T, fraction: Float) -> T, + type: SharedValueType<T, *>, canOverflow: Boolean ): AnimatedState<T> { return animateSharedValueAsState( @@ -86,7 +86,7 @@ private abstract class BaseElementScope<ContentScope>( element, key, value, - lerp, + type, canOverflow, ) } @@ -184,8 +184,7 @@ private fun shouldComposeMovableElement( fromSceneZIndex = layoutImpl.scenes.getValue(transition.fromScene).zIndex, toSceneZIndex = layoutImpl.scenes.getValue(transition.toScene).zIndex, ) != null - } - ?: return false + } ?: return false // Always compose movable elements in the scene picked by their scene picker. return shouldDrawOrComposeSharedElement( 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 0fc0053ce4a1..3cc8431cd87e 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 @@ internal fun Modifier.multiPointerDraggable( 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 @@ internal fun Modifier.multiPointerDraggable( enabled, startDragImmediately, onDragStarted, + swipeDetector, ) ) @@ -88,6 +90,7 @@ private data class MultiPointerDraggableElement( 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 @@ private data class MultiPointerDraggableElement( enabled = enabled, startDragImmediately = startDragImmediately, onDragStarted = onDragStarted, + swipeDetector = swipeDetector, ) override fun update(node: MultiPointerDraggableNode) { @@ -102,6 +106,7 @@ private data class MultiPointerDraggableElement( node.enabled = enabled node.startDragImmediately = startDragImmediately node.onDragStarted = onDragStarted + node.swipeDetector = swipeDetector } } @@ -111,6 +116,7 @@ internal class MultiPointerDraggableNode( var startDragImmediately: (startedPosition: Offset) -> Boolean, var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController, + var swipeDetector: SwipeDetector = DefaultSwipeDetector, ) : PointerInputModifierNode, DelegatingNode(), @@ -199,6 +205,7 @@ internal class MultiPointerDraggableNode( 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 @@ internal class MultiPointerDraggableNode( (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 @@ internal class MultiPointerDraggableNode( 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/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt index 6fef33c797d9..936f4ba0efef 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt @@ -24,7 +24,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.layout.approachLayout import androidx.compose.ui.platform.testTag @@ -69,7 +68,6 @@ internal class Scene( } @Composable - @OptIn(ExperimentalComposeUiApi::class) fun Content(modifier: Modifier = Modifier) { Box( modifier @@ -96,6 +94,7 @@ internal class SceneScopeImpl( private val layoutImpl: SceneTransitionLayoutImpl, private val scene: Scene, ) : SceneScope, ElementStateScope by layoutImpl.elementStateScope { + override val sceneKey: SceneKey = scene.key override val layoutState: SceneTransitionLayoutState = layoutImpl.state override fun Modifier.element(key: ElementKey): Modifier { @@ -124,7 +123,7 @@ internal class SceneScopeImpl( override fun <T> animateSceneValueAsState( value: T, key: ValueKey, - lerp: (T, T, Float) -> T, + type: SharedValueType<T, *>, canOverflow: Boolean ): AnimatedState<T> { return animateSharedValueAsState( @@ -133,7 +132,7 @@ internal class SceneScopeImpl( element = null, key = key, value = value, - lerp = lerp, + type = type, canOverflow = canOverflow, ) } 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 11e711ace971..2946b04af51a 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 @@ fun SceneTransitionLayout( 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 @@ fun SceneTransitionLayout( state, modifier, swipeSourceDetector, + swipeDetector, transitionInterceptionThreshold, onLayoutImpl = null, scenes, @@ -95,6 +97,7 @@ fun SceneTransitionLayout( 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 @@ fun SceneTransitionLayout( state, modifier, swipeSourceDetector, + swipeDetector, transitionInterceptionThreshold, scenes, ) @@ -163,6 +167,9 @@ interface ElementStateScope { @Stable @ElementDsl interface BaseSceneScope : ElementStateScope { + /** The key of this scene. */ + val sceneKey: SceneKey + /** The state of the [SceneTransitionLayout] in which this scene is contained. */ val layoutState: SceneTransitionLayoutState @@ -281,9 +288,7 @@ interface SceneScope : BaseSceneScope { * * @param value the value of this shared value in the current scene. * @param key the key of this shared value. - * @param lerp the *linear* interpolation function that should be used to interpolate between - * two different values. Note that it has to be linear because the [fraction] passed to this - * interpolator is already interpolated. + * @param type the [SharedValueType] of this animated value. * @param canOverflow whether this value can overflow past the values it is interpolated * between, for instance because the transition is animated using a bouncy spring. * @see animateSceneIntAsState @@ -295,11 +300,39 @@ interface SceneScope : BaseSceneScope { fun <T> animateSceneValueAsState( value: T, key: ValueKey, - lerp: (start: T, stop: T, fraction: Float) -> T, + type: SharedValueType<T, *>, canOverflow: Boolean, ): AnimatedState<T> } +/** + * The type of a shared value animated using [ElementScope.animateElementValueAsState] or + * [SceneScope.animateSceneValueAsState]. + */ +@Stable +interface SharedValueType<T, Delta> { + /** The unspecified value for this type. */ + val unspecifiedValue: T + + /** + * The zero value of this type. It should be equal to what [diff(x, x)] returns for any value of + * x. + */ + val zeroDeltaValue: Delta + + /** + * Return the linear interpolation of [a] and [b] at the given [progress], i.e. `a + (b - a) * + * progress`. + */ + fun lerp(a: T, b: T, progress: Float): T + + /** Return `a - b`. */ + fun diff(a: T, b: T): Delta + + /** Return `a + b * bWeight`. */ + fun addWeighted(a: T, b: Delta, bWeight: Float): T +} + @Stable @ElementDsl interface ElementScope<ContentScope> { @@ -308,9 +341,7 @@ interface ElementScope<ContentScope> { * * @param value the value of this shared value in the current scene. * @param key the key of this shared value. - * @param lerp the *linear* interpolation function that should be used to interpolate between - * two different values. Note that it has to be linear because the [fraction] passed to this - * interpolator is already interpolated. + * @param type the [SharedValueType] of this animated value. * @param canOverflow whether this value can overflow past the values it is interpolated * between, for instance because the transition is animated using a bouncy spring. * @see animateElementIntAsState @@ -322,7 +353,7 @@ interface ElementScope<ContentScope> { fun <T> animateElementValueAsState( value: T, key: ValueKey, - lerp: (start: T, stop: T, fraction: Float) -> T, + type: SharedValueType<T, *>, canOverflow: Boolean, ): AnimatedState<T> @@ -467,6 +498,7 @@ internal fun SceneTransitionLayoutForTesting( state: SceneTransitionLayoutState, modifier: Modifier = Modifier, swipeSourceDetector: SwipeSourceDetector = DefaultEdgeDetector, + swipeDetector: SwipeDetector = DefaultSwipeDetector, transitionInterceptionThreshold: Float = 0f, onLayoutImpl: ((SceneTransitionLayoutImpl) -> Unit)? = null, scenes: SceneTransitionLayoutScope.() -> Unit, @@ -502,5 +534,5 @@ internal fun SceneTransitionLayoutForTesting( 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 7856498aa365..5fa7c874c879 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 @@ -85,15 +85,14 @@ internal class SceneTransitionLayoutImpl( * The different values of a shared value keyed by a a [ValueKey] and the different elements and * scenes it is associated to. */ - private var _sharedValues: - MutableMap<ValueKey, MutableMap<ElementKey?, SnapshotStateMap<SceneKey, *>>>? = + private var _sharedValues: MutableMap<ValueKey, MutableMap<ElementKey?, SharedValue<*, *>>>? = null - internal val sharedValues: - MutableMap<ValueKey, MutableMap<ElementKey?, SnapshotStateMap<SceneKey, *>>> + internal val sharedValues: MutableMap<ValueKey, MutableMap<ElementKey?, SharedValue<*, *>>> get() = _sharedValues - ?: mutableMapOf<ValueKey, MutableMap<ElementKey?, SnapshotStateMap<SceneKey, *>>>() - .also { _sharedValues = it } + ?: mutableMapOf<ValueKey, MutableMap<ElementKey?, SharedValue<*, *>>>().also { + _sharedValues = it + } // TODO(b/317958526): Lazily allocate scene gesture handlers the first time they are needed. private val horizontalDraggableHandler: DraggableHandlerImpl @@ -185,14 +184,14 @@ internal class SceneTransitionLayoutImpl( } @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/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index a5b6d2486168..44affd968513 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -457,7 +457,7 @@ internal abstract class BaseSceneTransitionLayoutState( */ internal fun startTransition( transition: TransitionState.Transition, - transitionKey: TransitionKey?, + transitionKey: TransitionKey? = null, chain: Boolean = true, ) { checkThread() 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 000000000000..54ee78366875 --- /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 b618369c2369..171e2430c004 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 @@ import androidx.compose.ui.unit.IntSize * 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 data class SwipeToSceneElement( private class SwipeToSceneNode( draggableHandler: DraggableHandlerImpl, + swipeDetector: SwipeDetector, ) : DelegatingNode(), PointerInputModifierNode { private val delegate = delegate( @@ -55,6 +60,7 @@ private class SwipeToSceneNode( enabled = ::enabled, startDragImmediately = ::startDragImmediately, onDragStarted = draggableHandler::onDragStarted, + swipeDetector = swipeDetector, ) ) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt index a4682ff2a885..465a410a6bdb 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt @@ -20,6 +20,7 @@ import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.SpringSpec import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.android.compose.animation.scene.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified @@ -192,7 +193,7 @@ interface OverscrollBuilder : BaseTransitionBuilder { ) } -interface OverscrollScope { +interface OverscrollScope : Density { /** * Return the absolute distance between fromScene and toScene, if available, otherwise * [DistanceUnspecified]. diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt index e8854cf0de60..6e8b208ea9e8 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt @@ -32,7 +32,12 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.lerp import androidx.compose.ui.util.lerp import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestScenes.SceneA +import com.android.compose.animation.scene.TestScenes.SceneB +import com.android.compose.animation.scene.TestScenes.SceneC +import com.android.compose.animation.scene.TestScenes.SceneD import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows import org.junit.Rule import org.junit.Test @@ -130,8 +135,8 @@ class AnimatedSharedAsStateTest { // The transition lasts 64ms = 4 frames. spec = tween(durationMillis = 16 * 4, easing = LinearEasing) }, - fromScene = TestScenes.SceneA, - toScene = TestScenes.SceneB, + fromScene = SceneA, + toScene = SceneB, ) { before { assertThat(lastValueInFrom).isEqualTo(fromValues) @@ -189,8 +194,8 @@ class AnimatedSharedAsStateTest { // The transition lasts 64ms = 4 frames. spec = tween(durationMillis = 16 * 4, easing = LinearEasing) }, - fromScene = TestScenes.SceneA, - toScene = TestScenes.SceneB, + fromScene = SceneA, + toScene = SceneB, ) { before { assertThat(lastValueInFrom).isEqualTo(fromValues) @@ -243,8 +248,8 @@ class AnimatedSharedAsStateTest { // The transition lasts 64ms = 4 frames. spec = tween(durationMillis = 16 * 4, easing = LinearEasing) }, - fromScene = TestScenes.SceneA, - toScene = TestScenes.SceneB, + fromScene = SceneA, + toScene = SceneB, ) { before { assertThat(lastValueInFrom).isEqualTo(fromValues) @@ -381,4 +386,61 @@ class AnimatedSharedAsStateTest { } } } + + @Test + fun animatedValueIsUsingLastTransition() = runTest { + val state = + rule.runOnUiThread { MutableSceneTransitionLayoutStateImpl(SceneA, transitions {}) } + + val foo = ValueKey("foo") + val bar = ValueKey("bar") + val lastValues = mutableMapOf<ValueKey, MutableMap<SceneKey, Float>>() + + @Composable + fun SceneScope.animateFloat(value: Float, key: ValueKey) { + val animatedValue = animateSceneFloatAsState(value, key) + LaunchedEffect(animatedValue) { + snapshotFlow { animatedValue.value } + .collect { lastValues.getOrPut(key) { mutableMapOf() }[sceneKey] = it } + } + } + + rule.setContent { + SceneTransitionLayout(state) { + // foo goes from 0f to 100f in A => B. + scene(SceneA) { animateFloat(0f, foo) } + scene(SceneB) { animateFloat(100f, foo) } + + // bar goes from 0f to 10f in C => D. + scene(SceneC) { animateFloat(0f, bar) } + scene(SceneD) { animateFloat(10f, bar) } + } + } + + rule.runOnUiThread { + // A => B is at 30%. + state.startTransition( + transition( + from = SceneA, + to = SceneB, + progress = { 0.3f }, + onFinish = neverFinish(), + ) + ) + + // C => D is at 70%. + state.startTransition(transition(from = SceneC, to = SceneD, progress = { 0.7f })) + } + rule.waitForIdle() + + assertThat(lastValues[foo]?.get(SceneA)).isWithin(0.001f).of(30f) + assertThat(lastValues[foo]?.get(SceneB)).isWithin(0.001f).of(30f) + assertThat(lastValues[foo]?.get(SceneC)).isNull() + assertThat(lastValues[foo]?.get(SceneD)).isNull() + + assertThat(lastValues[bar]?.get(SceneA)).isNull() + assertThat(lastValues[bar]?.get(SceneB)).isNull() + assertThat(lastValues[bar]?.get(SceneC)).isWithin(0.001f).of(7f) + assertThat(lastValues[bar]?.get(SceneD)).isWithin(0.001f).of(7f) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 9692fae60400..beb74bc9bb36 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -59,6 +59,7 @@ import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.lerp +import androidx.compose.ui.util.lerp import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB @@ -348,7 +349,7 @@ class ElementTest { ), onLayoutImpl = { nullableLayoutImpl = it }, ) { - scene(SceneA) { /* Nothing */} + scene(SceneA) { /* Nothing */ } scene(SceneB) { Box(Modifier.element(key)) } scene(SceneC) { when (sceneCState) { @@ -1083,10 +1084,17 @@ class ElementTest { } val layoutSize = DpSize(200.dp, 100.dp) + val lastValues = mutableMapOf<SceneKey, Float>() @Composable - fun SceneScope.Foo(size: Dp, modifier: Modifier = Modifier) { - Box(modifier.element(TestElements.Foo).size(size)) + fun SceneScope.Foo(size: Dp, value: Float, modifier: Modifier = Modifier) { + val sceneKey = this.sceneKey + Element(TestElements.Foo, modifier.size(size)) { + val animatedValue = animateElementFloatAsState(value, TestValues.Value1) + LaunchedEffect(animatedValue) { + snapshotFlow { animatedValue.value }.collect { lastValues[sceneKey] = it } + } + } } // The size of Foo when idle in A, B or C. @@ -1094,6 +1102,11 @@ class ElementTest { val sizeInB = 30.dp val sizeInC = 50.dp + // The target value when idle in A, B, or C. + val valueInA = 0f + val valueInB = 100f + val valueInC = 200f + lateinit var layoutImpl: SceneTransitionLayoutImpl rule.setContent { SceneTransitionLayoutForTesting( @@ -1103,7 +1116,9 @@ class ElementTest { ) { // In scene A, Foo is aligned at the TopStart. scene(SceneA) { - Box(Modifier.fillMaxSize()) { Foo(sizeInA, Modifier.align(Alignment.TopStart)) } + Box(Modifier.fillMaxSize()) { + Foo(sizeInA, valueInA, Modifier.align(Alignment.TopStart)) + } } // In scene C, Foo is aligned at the BottomEnd, so it moves vertically when coming @@ -1111,14 +1126,16 @@ class ElementTest { // values and deltas are properly cleared once all transitions are done. scene(SceneC) { Box(Modifier.fillMaxSize()) { - Foo(sizeInC, Modifier.align(Alignment.BottomEnd)) + Foo(sizeInC, valueInC, Modifier.align(Alignment.BottomEnd)) } } // In scene B, Foo is aligned at the TopEnd, so it moves horizontally when coming // from A. scene(SceneB) { - Box(Modifier.fillMaxSize()) { Foo(sizeInB, Modifier.align(Alignment.TopEnd)) } + Box(Modifier.fillMaxSize()) { + Foo(sizeInB, valueInB, Modifier.align(Alignment.TopEnd)) + } } } } @@ -1134,6 +1151,10 @@ class ElementTest { .assertSizeIsEqualTo(sizeInA) .assertPositionInRootIsEqualTo(offsetInA.x, offsetInA.y) + assertThat(lastValues[SceneA]).isWithin(0.001f).of(valueInA) + assertThat(lastValues[SceneB]).isNull() + assertThat(lastValues[SceneC]).isNull() + // Current transition is A => B at 50%. val aToBProgress = 0.5f val aToB = @@ -1145,12 +1166,17 @@ class ElementTest { ) val offsetInAToB = lerp(offsetInA, offsetInB, aToBProgress) val sizeInAToB = lerp(sizeInA, sizeInB, aToBProgress) + val valueInAToB = lerp(valueInA, valueInB, aToBProgress) rule.runOnUiThread { state.startTransition(aToB, transitionKey = null) } rule .onNode(isElement(TestElements.Foo, SceneB)) .assertSizeIsEqualTo(sizeInAToB) .assertPositionInRootIsEqualTo(offsetInAToB.x, offsetInAToB.y) + assertThat(lastValues[SceneA]).isWithin(0.001f).of(valueInAToB) + assertThat(lastValues[SceneB]).isWithin(0.001f).of(valueInAToB) + assertThat(lastValues[SceneC]).isNull() + // Start B => C at 0%. var bToCProgress by mutableFloatStateOf(0f) var interruptionProgress by mutableFloatStateOf(1f) @@ -1167,6 +1193,11 @@ class ElementTest { // to the current transition offset and size. val offsetInterruptionDelta = offsetInAToB - offsetInB val sizeInterruptionDelta = sizeInAToB - sizeInB + val valueInterruptionDelta = valueInAToB - valueInB + + assertThat(offsetInterruptionDelta).isNotEqualTo(DpOffset.Zero) + assertThat(sizeInterruptionDelta).isNotEqualTo(0.dp) + assertThat(valueInterruptionDelta).isNotEqualTo(0f) // Interruption progress is at 100% and bToC is at 0%, so Foo should be at the same offset // and size as right before the interruption. @@ -1175,11 +1206,16 @@ class ElementTest { .assertPositionInRootIsEqualTo(offsetInAToB.x, offsetInAToB.y) .assertSizeIsEqualTo(sizeInAToB) + assertThat(lastValues[SceneA]).isWithin(0.001f).of(valueInAToB) + assertThat(lastValues[SceneB]).isWithin(0.001f).of(valueInAToB) + assertThat(lastValues[SceneC]).isWithin(0.001f).of(valueInAToB) + // Move the transition forward at 30% and set the interruption progress to 50%. bToCProgress = 0.3f interruptionProgress = 0.5f val offsetInBToC = lerp(offsetInB, offsetInC, bToCProgress) val sizeInBToC = lerp(sizeInB, sizeInC, bToCProgress) + val valueInBToC = lerp(valueInB, valueInC, bToCProgress) val offsetInBToCWithInterruption = offsetInBToC + DpOffset( @@ -1187,6 +1223,9 @@ class ElementTest { offsetInterruptionDelta.y * interruptionProgress, ) val sizeInBToCWithInterruption = sizeInBToC + sizeInterruptionDelta * interruptionProgress + val valueInBToCWithInterruption = + valueInBToC + valueInterruptionDelta * interruptionProgress + rule.waitForIdle() rule .onNode(isElement(TestElements.Foo, SceneB)) @@ -1196,6 +1235,10 @@ class ElementTest { ) .assertSizeIsEqualTo(sizeInBToCWithInterruption) + assertThat(lastValues[SceneA]).isWithin(0.001f).of(valueInBToCWithInterruption) + assertThat(lastValues[SceneB]).isWithin(0.001f).of(valueInBToCWithInterruption) + assertThat(lastValues[SceneC]).isWithin(0.001f).of(valueInBToCWithInterruption) + // Finish the transition and interruption. bToCProgress = 1f interruptionProgress = 0f 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 aa6d1130fc2a..4bb643f8b89e 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.Offset 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 @@ class MultiPointerDraggableTest { 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/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt index 3751a229b690..08532bd72e33 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt @@ -461,4 +461,30 @@ class SceneTransitionLayoutTest { assertThat(exception).hasMessageThat().contains(Back.toString()) assertThat(exception).hasMessageThat().contains(SceneA.debugName) } + + @Test + fun sceneKeyInScope() { + val state = rule.runOnUiThread { MutableSceneTransitionLayoutState(SceneA) } + + var keyInA: SceneKey? = null + var keyInB: SceneKey? = null + var keyInC: SceneKey? = null + rule.setContent { + SceneTransitionLayout(state) { + scene(SceneA) { keyInA = sceneKey } + scene(SceneB) { keyInB = sceneKey } + scene(SceneC) { keyInC = sceneKey } + } + } + + // Snap to B then C to compose these scenes at least once. + rule.runOnUiThread { state.snapToScene(SceneB) } + rule.waitForIdle() + rule.runOnUiThread { state.snapToScene(SceneC) } + rule.waitForIdle() + + assertThat(keyInA).isEqualTo(SceneA) + assertThat(keyInB).isEqualTo(SceneB) + assertThat(keyInC).isEqualTo(SceneC) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index 3a806a44be64..25ea2eef85e9 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.assertPositionInRootIsEqualTo import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performTouchInput @@ -594,4 +595,43 @@ class SwipeToSceneTest { assertThat(transition).hasToScene(SceneB) assertThat(transition).hasProgress(0.5f, tolerance = 0.01f) } + + @Test + fun overscrollScopeExtendsDensity() { + val swipeDistance = 100.dp + val state = + rule.runOnUiThread { + MutableSceneTransitionLayoutState( + SceneA, + transitions { + from(SceneA, to = SceneB) { distance = FixedDistance(swipeDistance) } + + overscroll(SceneB, Orientation.Vertical) { + translate(TestElements.Foo, x = { 20.dp.toPx() }, y = { 30.dp.toPx() }) + } + } + ) + } + val layoutSize = 200.dp + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(state, Modifier.size(layoutSize)) { + scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + Box(Modifier.fillMaxSize()) + } + scene(SceneB) { Box(Modifier.element(TestElements.Foo).fillMaxSize()) } + } + } + + // Swipe down by twice the swipe distance so that we are at 100% overscrolling on scene B. + rule.onRoot().performTouchInput { + val middle = (layoutSize / 2).toPx() + down(Offset(middle, middle)) + moveBy(Offset(0f, touchSlop + (swipeDistance * 2).toPx()), delayMillis = 1_000) + } + + // Foo should be translated by (20dp, 30dp). + rule.onNode(isElement(TestElements.Foo)).assertPositionInRootIsEqualTo(20.dp, 30.dp) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt index a609be48a225..e6fa69d87b34 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/Transition.kt @@ -41,8 +41,10 @@ fun transition( return object : TransitionState.Transition(from, to), TransitionState.HasOverscrollProperties { override val currentScene: SceneKey get() = current() + override val progress: Float get() = progress() + override val progressVelocity: Float get() = progressVelocity() @@ -53,6 +55,8 @@ fun transition( override val orientation: Orientation = orientation override val overscrollScope: OverscrollScope = object : OverscrollScope { + override val density: Float = 1f + override val fontScale: Float = 1f override val absoluteDistance = 0f } diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt index b39201427b46..502dbe3e423c 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt @@ -239,6 +239,8 @@ class DefaultClockController( } inner class DefaultClockEvents : ClockEvents { + override var isReactiveTouchInteractionEnabled: Boolean = false + override fun onTimeFormatChanged(is24Hr: Boolean) = clocks.forEach { it.refreshFormat(is24Hr) } 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 165e9728b9d7..de9baa59b2b7 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 @@ public class Assert { } } + /** + * 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 e39ad4f0b405..a676c7db4290 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 android.view.Display 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 @@ class ScreenBrightnessDisplayManagerRepositoryTest : SysuiTestCase() { 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 33c44f8a331e..b6616bf0c8de 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.ext.junit.runners.AndroidJUnit4 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 @@ class ScreenBrightnessInteractorTest : SysuiTestCase() { 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 0058ee4a9c4e..8402676dbd6b 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.ext.junit.runners.AndroidJUnit4 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 @@ class BrightnessSliderViewModelTest : SysuiTestCase() { BrightnessSliderViewModel( screenBrightnessInteractor, brightnessPolicyEnforcementInteractor, + applicationCoroutineScope, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt index 45e7d8a43c2a..fd0bf4dae198 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalRepositoryImplTest.kt @@ -41,6 +41,7 @@ class CommunalRepositoryImplTest : SysuiTestCase() { private val underTest by lazy { CommunalSceneRepositoryImpl( kosmos.applicationCoroutineScope, + kosmos.applicationCoroutineScope, kosmos.sceneDataSource, ) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt index 89c54956b9a3..fb2b33d70c47 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt @@ -34,6 +34,8 @@ import com.android.systemui.Flags.FLAG_COMMUNAL_HUB import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.communal.data.model.DisabledReason +import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING +import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.fakeFeatureFlagsClassic @@ -43,6 +45,7 @@ import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test @@ -216,6 +219,32 @@ class CommunalSettingsRepositoryImplTest : SysuiTestCase() { ) } + @Test + fun backgroundType_defaultValue() = + testScope.runTest { + val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER)) + assertThat(backgroundType).isEqualTo(CommunalBackgroundType.DEFAULT) + } + + @Test + fun backgroundType_verifyAllValues() = + testScope.runTest { + val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER)) + for (type in CommunalBackgroundType.entries) { + kosmos.fakeSettings.putIntForUser( + GLANCEABLE_HUB_BACKGROUND_SETTING, + type.value, + PRIMARY_USER.id + ) + assertWithMessage( + "Expected $type when $GLANCEABLE_HUB_BACKGROUND_SETTING is set to" + + " ${type.value} but was $backgroundType" + ) + .that(backgroundType) + .isEqualTo(type) + } + } + private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id))) .thenReturn(disabledFlags) 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 5e19a41f345c..9dcea82e782b 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 @@ -37,6 +37,7 @@ import com.android.systemui.communal.data.repository.fakeCommunalTutorialReposit import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.communalSceneInteractor +import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.communal.domain.interactor.communalTutorialInteractor import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.shared.model.CommunalScenes @@ -54,6 +55,8 @@ 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.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 +65,8 @@ import com.android.systemui.kosmos.testScope 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 +76,6 @@ import com.android.systemui.smartspace.data.repository.fakeSmartspaceRepository 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 +89,7 @@ import org.mockito.Mock 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 +143,8 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { ) whenever(providerInfo.profile).thenReturn(UserHandle(MAIN_USER_INFO.id)) + kosmos.powerInteractor.setAwakeForTest() + underTest = CommunalViewModel( testScope, @@ -146,6 +153,7 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { kosmos.keyguardInteractor, kosmos.communalSceneInteractor, kosmos.communalInteractor, + kosmos.communalSettingsInteractor, kosmos.communalTutorialInteractor, kosmos.shadeInteractor, mediaHost, @@ -468,6 +476,229 @@ class CommunalViewModelTest(flags: FlagsParameterization) : SysuiTestCase() { 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 723f6a2bfff4..9300db9a24c8 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 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private lateinit var fakeWakeLockBuilder: WakeLockFake.Builder - private lateinit var fakeWakeLock: WakeLockFake - - @Mock private lateinit var taskFragmentComponentFactory: TaskFragmentComponent.Factory - @Mock private lateinit var taskFragmentComponent: TaskFragmentComponent - @Mock private lateinit var activity: Activity + private val fakeWakeLock = WakeLockFake() + private val fakeWakeLockBuilder by lazy { + WakeLockFake.Builder(context).apply { setWakeLock(fakeWakeLock) } + } + + 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 val taskFragmentComponentFactory = + mock<TaskFragmentComponent.Factory> { + on { + create( + activity = eq(activity), + onCreateCallback = onCreateCallback.capture(), + onInfoChangedCallback = onInfoChangedCallback.capture(), + hide = hideCallback.capture(), + ) + } doReturn taskFragmentComponent + } - private lateinit var underTest: HomeControlsDreamService + 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 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { @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 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { underTest.onAttachedToWindow() assertThat(fakeWakeLock.isHeld).isTrue() } + @Test fun testDetachWindow_wakeLockCanBeReleased() = testScope.runTest { @@ -112,14 +134,60 @@ class HomeControlsDreamServiceTest : SysuiTestCase() { 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 c51413a2cc78..4849e66d37d5 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.haptics.vibratorHelper 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 @@ class QSLongPressEffectTest : SysuiTestCase() { 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 @@ class QSLongPressEffectTest : SysuiTestCase() { vibratorHelper, kosmos.keyguardInteractor, ) + longPressEffect.qsTile = qsTile } @Test @@ -91,8 +94,10 @@ class QSLongPressEffectTest : SysuiTestCase() { // 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 @@ class QSLongPressEffectTest : SysuiTestCase() { } @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 @@ class QSLongPressEffectTest : SysuiTestCase() { // 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 @@ class QSLongPressEffectTest : SysuiTestCase() { 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 d630a2f64c5f..6c5001ab9415 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 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { @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 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { ) // Detect a power gesture and then wake up. + kosmos.fakeKeyguardRepository.setKeyguardDismissible(true) reset(transitionRepository) powerInteractor.onCameraLaunchGestureDetected() powerInteractor.setAwakeForTest() @@ -355,6 +356,7 @@ class FromAodTransitionInteractorTest : SysuiTestCase() { ) // 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 index bfc777509c7b..612f2e73e4bb 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.Companion.se 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 @@ class FromDozingTransitionInteractorTest : SysuiTestCase() { ) // 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/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardIndicationAreaViewModelTest.kt index 04c270d07b0a..ad24a711e9b7 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.data.repository.FakeKeyguardRepository 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 @@ class KeyguardIndicationAreaViewModelTest : SysuiTestCase() { 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 37d472169ae5..7ebebd7afa91 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 @@ class TileSpecSettingsRepositoryTest : SysuiTestCase() { .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 58fc10917d44..b12fbc2066a2 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 @@ class UserTileSpecRepositoryTest : SysuiTestCase() { 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 1c73fe2b305d..6ad4b317b94c 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.TileSpec 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 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { 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 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { 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 260189d401d2..e8ad038f8fbc 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.MinimumTilesFixedReposit 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 @@ class NoLowNumberOfTilesTest : SysuiTestCase() { } } + @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/scene/domain/startable/ScrimStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/ScrimStartableTest.kt new file mode 100644 index 000000000000..0e90afe2ebcd --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/ScrimStartableTest.kt @@ -0,0 +1,452 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.scene.domain.startable + +import androidx.test.filters.SmallTest +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.SysuiTestCase +import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.domain.interactor.biometricUnlockInteractor +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource +import com.android.systemui.keyguard.shared.model.DozeStateModel +import com.android.systemui.keyguard.shared.model.DozeTransitionModel +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.setSceneTransition +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor +import com.android.systemui.statusbar.domain.interactor.keyguardOcclusionInteractor +import com.android.systemui.statusbar.phone.BiometricUnlockController +import com.android.systemui.statusbar.phone.ScrimState +import com.android.systemui.statusbar.phone.centralSurfaces +import com.android.systemui.statusbar.phone.dozeServiceHost +import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import com.google.common.truth.Truth.assertWithMessage +import kotlin.reflect.full.memberProperties +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.BeforeClass +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever +import platform.test.runner.parameterized.Parameter +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters + +@SmallTest +@RunWith(ParameterizedAndroidJunit4::class) +@EnableSceneContainer +class ScrimStartableTest : SysuiTestCase() { + + companion object { + @Parameters(name = "{0}") + @JvmStatic + fun testSpecs(): List<TestSpec> { + return listOf( + TestSpec( + id = 0, + expectedState = ScrimState.KEYGUARD, + Preconditions( + isOnKeyguard = true, + isAlternateBouncerVisible = true, + isTransitioningAwayFromKeyguard = true, + ), + ), + TestSpec( + id = 1, + expectedState = null, + Preconditions( + isOnKeyguard = true, + isAlternateBouncerVisible = true, + isTransitioningToShade = true, + ), + ), + TestSpec( + id = 2, + expectedState = ScrimState.BOUNCER, + Preconditions( + isOnKeyguard = true, + isCurrentSceneBouncer = true, + ), + ), + TestSpec( + id = 3, + expectedState = ScrimState.BOUNCER_SCRIMMED, + Preconditions( + isOnKeyguard = true, + isCurrentSceneBouncer = true, + isBouncerScrimmingNeeded = true, + ), + ), + TestSpec( + id = 4, + expectedState = ScrimState.BRIGHTNESS_MIRROR, + Preconditions( + isOnKeyguard = true, + isBrightnessMirrorVisible = true, + ), + ), + TestSpec( + id = 5, + expectedState = ScrimState.BRIGHTNESS_MIRROR, + Preconditions( + isOnKeyguard = true, + isCurrentSceneBouncer = true, + isBiometricWakeAndUnlock = true, + isBrightnessMirrorVisible = true, + ), + ), + TestSpec( + id = 6, + expectedState = ScrimState.SHADE_LOCKED, + Preconditions( + isOnKeyguard = true, + isCurrentSceneShade = true, + ), + ), + TestSpec( + id = 7, + expectedState = ScrimState.PULSING, + Preconditions( + isOnKeyguard = true, + isDozing = true, + isPulsing = true, + ), + ), + TestSpec( + id = 8, + expectedState = ScrimState.OFF, + Preconditions( + isOnKeyguard = true, + hasPendingScreenOffCallback = true, + ), + ), + TestSpec( + id = 9, + expectedState = ScrimState.AOD, + Preconditions( + isOnKeyguard = true, + isDozing = true, + ), + ), + TestSpec( + id = 10, + expectedState = ScrimState.GLANCEABLE_HUB, + Preconditions( + isIdleOnCommunal = true, + ), + ), + TestSpec( + id = 11, + expectedState = ScrimState.GLANCEABLE_HUB_OVER_DREAM, + Preconditions(isIdleOnCommunal = true, isDreaming = true), + ), + TestSpec( + id = 12, + expectedState = ScrimState.UNLOCKED, + Preconditions( + isDeviceEntered = true, + ), + ), + TestSpec( + id = 13, + expectedState = ScrimState.UNLOCKED, + Preconditions( + isOnKeyguard = true, + isBiometricWakeAndUnlock = true, + ), + ), + TestSpec( + id = 14, + expectedState = ScrimState.KEYGUARD, + Preconditions(), + ), + TestSpec( + id = 15, + expectedState = ScrimState.DREAMING, + Preconditions( + isOnKeyguard = true, + isOccluded = true, + isDreaming = true, + ), + ), + TestSpec( + id = 16, + expectedState = ScrimState.UNLOCKED, + Preconditions( + isOnKeyguard = true, + isOccluded = true, + ), + ), + ) + } + + @BeforeClass + @JvmStatic + fun setUpClass() { + val seenIds = mutableSetOf<Int>() + testSpecs().forEach { testSpec -> + assertWithMessage("Duplicate TestSpec id=${testSpec.id}") + .that(seenIds) + .doesNotContain(testSpec.id) + seenIds.add(testSpec.id) + } + } + } + + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private val underTest = kosmos.scrimStartable + + @JvmField @Parameter(0) var testSpec: TestSpec? = null + + @Before + fun setUp() { + kosmos.dozeServiceHost.initialize( + /* centralSurfaces= */ kosmos.centralSurfaces, + /* statusBarKeyguardViewManager= */ kosmos.statusBarKeyguardViewManager, + /* notificationShadeWindowViewController= */ mock(), + /* ambientIndicationContainer= */ mock(), + ) + underTest.start() + } + + @Test + fun test() = + testScope.runTest { + val observedState by collectLastValue(underTest.scrimState) + val preconditions = checkNotNull(testSpec).preconditions + preconditions.assertValid() + + setUpWith(preconditions) + + runCurrent() + + assertThat(observedState).isEqualTo(checkNotNull(testSpec).expectedState) + } + + /** Sets up the state to match what's specified in the given [preconditions]. */ + private fun TestScope.setUpWith( + preconditions: Preconditions, + ) { + kosmos.fakeKeyguardBouncerRepository.setAlternateVisible( + preconditions.isAlternateBouncerVisible + ) + + if (preconditions.isDeviceEntered) { + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + whenIdle(on = Scenes.Gone) + } else { + whenIdle(on = Scenes.Lockscreen) + } + runCurrent() + + when { + preconditions.isTransitioningToShade -> + whenTransitioning( + from = Scenes.Lockscreen, + to = Scenes.Shade, + ) + preconditions.isTransitioningAwayFromKeyguard -> + whenTransitioning( + from = Scenes.Lockscreen, + to = Scenes.Gone, + ) + preconditions.isCurrentSceneShade -> whenIdle(on = Scenes.Shade) + preconditions.isCurrentSceneBouncer -> whenIdle(on = Scenes.Bouncer) + preconditions.isIdleOnCommunal -> whenIdle(on = Scenes.Communal) + } + + kosmos.keyguardOcclusionInteractor.setWmNotifiedShowWhenLockedActivityOnTop( + showWhenLockedActivityOnTop = preconditions.isOccluded, + taskInfo = if (preconditions.isOccluded) mock() else null, + ) + + if (preconditions.isBiometricWakeAndUnlock) { + kosmos.biometricUnlockInteractor.setBiometricUnlockState( + BiometricUnlockController.MODE_WAKE_AND_UNLOCK, + BiometricUnlockSource.FINGERPRINT_SENSOR, + ) + } + + kosmos.brightnessMirrorShowingInteractor.setMirrorShowing( + preconditions.isBrightnessMirrorVisible + ) + + if (preconditions.hasPendingScreenOffCallback) { + kosmos.dozeServiceHost.prepareForGentleSleep {} + } else { + kosmos.dozeServiceHost.cancelGentleSleep() + } + + kosmos.fakeKeyguardRepository.setIsDozing(preconditions.isDozing) + if (preconditions.isPulsing) { + kosmos.fakeKeyguardRepository.setDozeTransitionModel( + DozeTransitionModel(to = DozeStateModel.DOZE_PULSING) + ) + } + kosmos.fakeKeyguardRepository.setDreaming(preconditions.isDreaming) + + whenever(kosmos.statusBarKeyguardViewManager.primaryBouncerNeedsScrimming()) + .thenReturn(preconditions.isBouncerScrimmingNeeded) + + runCurrent() + } + + /** Sets up an idle state on the given [on] scene. */ + private fun whenIdle(on: SceneKey) { + kosmos.setSceneTransition(ObservableTransitionState.Idle(on)) + kosmos.sceneInteractor.changeScene(on, "") + } + + /** Sets up a transitioning state between the [given] and [to] scenes. */ + private fun whenTransitioning(from: SceneKey, to: SceneKey, progress: Float = 0.5f) { + val currentScene = if (progress > 0.5f) to else from + kosmos.setSceneTransition( + ObservableTransitionState.Transition( + fromScene = from, + toScene = to, + progress = flowOf(progress), + currentScene = flowOf(currentScene), + isInitiatedByUserInput = true, + isUserInputOngoing = flowOf(false), + ) + ) + kosmos.sceneInteractor.changeScene(currentScene, "") + } + + data class Preconditions( + /** Whether bouncer or lockscreen scene is in the nav stack. */ + val isOnKeyguard: Boolean = false, + val isAlternateBouncerVisible: Boolean = false, + /** Whether any non-shade nor QS scene is transitioning to a shade or QS scene. */ + val isTransitioningToShade: Boolean = false, + val isOccluded: Boolean = false, + val isCurrentSceneBouncer: Boolean = false, + val isBiometricWakeAndUnlock: Boolean = false, + /** Whether there's an active transition from lockscreen or bouncer to gone. */ + val isTransitioningAwayFromKeyguard: Boolean = false, + val isBrightnessMirrorVisible: Boolean = false, + /** Whether the current scene is a shade or QS scene. */ + val isCurrentSceneShade: Boolean = false, + val isDeviceEntered: Boolean = false, + val isPulsing: Boolean = false, + val hasPendingScreenOffCallback: Boolean = false, + val isDozing: Boolean = false, + val isIdleOnCommunal: Boolean = false, + val isDreaming: Boolean = false, + val isBouncerScrimmingNeeded: Boolean = false, + ) { + override fun toString(): String { + // Only include values overridden to true: + return buildString { + append("(") + append( + Preconditions::class + .memberProperties + .filter { it.get(this@Preconditions) == true } + .joinToString(", ") { "${it.name}=true" } + ) + append(")") + } + } + + fun assertValid() { + assertWithMessage("isOccluded cannot be true without isOnKeyguard also being true") + .that(!isOccluded || isOnKeyguard) + .isTrue() + + assertWithMessage( + "isCurrentSceneBouncer cannot be true without isOnKeyguard also being true" + ) + .that(!isCurrentSceneBouncer || isOnKeyguard) + .isTrue() + + assertWithMessage( + "isTransitioningAwayFromKeyguard cannot be true without isOnKeyguard being true" + ) + .that(!isTransitioningAwayFromKeyguard || isOnKeyguard) + .isTrue() + + assertWithMessage( + "isCurrentSceneBouncer cannot be true at the same time as isCurrentSceneShade" + ) + .that(!isCurrentSceneBouncer || !isCurrentSceneShade) + .isTrue() + + assertWithMessage( + "isCurrentSceneBouncer cannot be true at the same time as isIdleOnCommunal" + ) + .that(!isCurrentSceneBouncer || !isIdleOnCommunal) + .isTrue() + + assertWithMessage( + "isCurrentSceneShade cannot be true at the same time as isIdleOnCommunal" + ) + .that(!isCurrentSceneShade || !isIdleOnCommunal) + .isTrue() + + assertWithMessage("isDeviceEntered cannot be true at the same time as isOnKeyguard") + .that(!isDeviceEntered || !isOnKeyguard) + .isTrue() + + assertWithMessage( + "isDeviceEntered cannot be true at the same time as isCurrentSceneBouncer" + ) + .that(!isDeviceEntered || !isCurrentSceneBouncer) + .isTrue() + + assertWithMessage( + "isDeviceEntered cannot be true at the same time as isAlternateBouncerVisible" + ) + .that(!isDeviceEntered || !isAlternateBouncerVisible) + .isTrue() + + assertWithMessage("isPulsing cannot be true if both isDozing is false") + .that(!isPulsing || isDozing) + .isTrue() + } + } + + data class TestSpec( + val id: Int, + val expectedState: ScrimState?, + val preconditions: Preconditions, + ) { + override fun toString(): String { + return "id=$id, expected=$expectedState, preconditions=$preconditions" + } + } +} 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 02993b8922b0..523a89ad5740 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.Mock; 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 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { 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 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { 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 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @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 82e2bb719818..c35c165ba761 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.data.repository.fakeKeyguardTransitionRepos 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.kosmos.testScope 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 @@ class SharedNotificationContainerViewModelTest(flags: FlagsParameterization) : S @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 63f19fbdfed9..6b5d07282a08 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 @@ class AvalancheControllerTest : SysuiTestCase() { // 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 3bfc046e46b4..88bef91d043f 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 static org.mockito.Mockito.when; 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 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { @Override public void SysuiSetup() throws Exception { super.SysuiSetup(); - mAvalancheController = new AvalancheController(dumpManager); + mAvalancheController = new AvalancheController(dumpManager, mUiEventLoggerFake); } @Test @@ -610,7 +611,31 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { } @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 9feb914c56e9..200e92e4370b 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 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { 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 b83b93b8f77e..10a4eb790b44 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.eq 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 @@ class AudioOutputInteractorTest : SysuiTestCase() { 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 @@ class AudioOutputInteractorTest : SysuiTestCase() { 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 000000000000..8921a23fda8d --- /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 d497b4ae35cb..86a20dccc8fa 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.testKosmos 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 @@ class MediaOutputViewModelTest : SysuiTestCase() { 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 000000000000..7934b02126bd --- /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/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt index 629c96c0ef44..c7998f089a6d 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt @@ -153,6 +153,9 @@ class DefaultClockFaceLayout(val view: View) : ClockFaceLayout { /** Events that should call when various rendering parameters change */ interface ClockEvents { + /** Set to enable or disable swipe interaction */ + var isReactiveTouchInteractionEnabled: Boolean + /** Call whenever timezone changes */ fun onTimeZoneChanged(timeZone: TimeZone) 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 3244eb43c8c4..bf58eee9a9ce 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 @@ public interface QS extends FragmentBase { 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 000000000000..82b222e7625a --- /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 4a2a1cb9dc6d..000000000000 --- 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 fad41c822ec0..000000000000 --- 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 c09607d19bdd..000000000000 --- 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 1c31f1da0681..000000000000 --- 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 a8abd793bd00..000000000000 --- 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 0881d7c5c2b5..000000000000 --- 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 4a5d4af96497..000000000000 --- 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 47a2965bcfac..000000000000 --- 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 0b0055b1f020..000000000000 --- 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 29a014a713f7..000000000000 --- 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-sw600dp/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml deleted file mode 100644 index 8b9eabc5bd93..000000000000 --- a/packages/SystemUI/res/layout-sw600dp/biometric_prompt_constraint_layout.xml +++ /dev/null @@ -1,237 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:app="http://schemas.android.com/apk/res-auto" - xmlns:tools="http://schemas.android.com/tools" - xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" - android:id="@+id/biometric_prompt_constraint_layout" - android:layout_width="match_parent" - android:layout_height="match_parent"> - - <ImageView - android:id="@+id/background" - android:layout_width="0dp" - android:layout_height="0dp" - android:contentDescription="@string/biometric_dialog_empty_space_description" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <View - android:id="@+id/panel" - style="@style/AuthCredentialPanelStyle" - android:layout_width="0dp" - android:layout_height="0dp" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toStartOf="@+id/rightGuideline" - app:layout_constraintStart_toStartOf="@+id/leftGuideline" - app:layout_constraintTop_toTopOf="@+id/topBarrier" - app:layout_constraintWidth_max="640dp" /> - - <include - android:id="@+id/button_bar" - layout="@layout/biometric_prompt_button_bar" - android:layout_width="0dp" - android:layout_height="wrap_content" - app:layout_constraintBottom_toBottomOf="@id/bottomGuideline" - app:layout_constraintEnd_toEndOf="@id/panel" - app:layout_constraintStart_toStartOf="@id/panel" /> - - <ScrollView - android:id="@+id/scrollView" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:fadeScrollbars="false" - android:fillViewport="true" - android:paddingBottom="32dp" - android:paddingHorizontal="32dp" - android:paddingTop="24dp" - app:layout_constrainedHeight="true" - app:layout_constrainedWidth="true" - app:layout_constraintBottom_toTopOf="@+id/scrollBarrier" - app:layout_constraintEnd_toEndOf="@id/panel" - app:layout_constraintStart_toStartOf="@id/panel" - app:layout_constraintTop_toTopOf="@+id/topGuideline" - app:layout_constraintVertical_bias="1.0"> - - <androidx.constraintlayout.widget.ConstraintLayout - android:id="@+id/innerConstraint" - android:layout_width="match_parent" - android:layout_height="wrap_content"> - - <ImageView - android:id="@+id/logo" - android:layout_width="@dimen/biometric_prompt_logo_size" - android:layout_height="@dimen/biometric_prompt_logo_size" - android:layout_gravity="center" - android:contentDescription="@string/biometric_dialog_logo" - android:scaleType="fitXY" - android:visibility="visible" - app:layout_constraintBottom_toTopOf="@+id/logo_description" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> - - <TextView - android:id="@+id/logo_description" - style="@style/TextAppearance.AuthCredential.LogoDescription" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:paddingTop="16dp" - app:layout_constraintBottom_toTopOf="@+id/title" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/logo" /> - - <TextView - android:id="@+id/title" - style="@style/TextAppearance.AuthCredential.Title" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="@integer/biometric_dialog_text_gravity" - android:paddingTop="16dp" - app:layout_constraintBottom_toTopOf="@+id/subtitle" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/logo_description" /> - - <TextView - android:id="@+id/subtitle" - style="@style/TextAppearance.AuthCredential.Subtitle" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="@integer/biometric_dialog_text_gravity" - android:paddingTop="16dp" - app:layout_constraintBottom_toTopOf="@+id/contentBarrier" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/title" /> - - <LinearLayout - android:id="@+id/customized_view_container" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:gravity="center_vertical" - android:orientation="vertical" - android:paddingTop="24dp" - android:visibility="gone" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/subtitle" /> - - <TextView - android:id="@+id/description" - style="@style/TextAppearance.AuthCredential.Description" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:gravity="@integer/biometric_dialog_text_gravity" - android:paddingTop="16dp" - android:textAlignment="viewStart" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/subtitle" /> - - <androidx.constraintlayout.widget.Barrier - android:id="@+id/contentBarrier" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierAllowsGoneWidgets="false" - app:barrierDirection="top" - app:constraint_referenced_ids="description, customized_view_container" /> - - </androidx.constraintlayout.widget.ConstraintLayout> - </ScrollView> - - <!-- Cancel Button, replaces negative button when biometric is accepted --> - <TextView - android:id="@+id/indicator" - style="@style/TextAppearance.AuthCredential.Indicator" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginTop="24dp" - android:accessibilityLiveRegion="assertive" - android:fadingEdge="horizontal" - android:gravity="center_horizontal" - android:scrollHorizontally="true" - app:layout_constraintBottom_toTopOf="@+id/button_bar" - app:layout_constraintEnd_toEndOf="@+id/panel" - app:layout_constraintStart_toStartOf="@+id/panel" - app:layout_constraintTop_toBottomOf="@+id/biometric_icon" - app:layout_constraintVertical_bias="0.0" /> - - <!-- "Use Credential" Button, replaces if device credential is allowed --> - - <!-- Positive Button --> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/topBarrier" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierAllowsGoneWidgets="false" - app:barrierDirection="top" - app:constraint_referenced_ids="scrollView" /> - - <!-- Try Again Button --> - <androidx.constraintlayout.widget.Barrier - android:id="@+id/scrollBarrier" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:barrierAllowsGoneWidgets="false" - app:barrierDirection="top" - app:constraint_referenced_ids="biometric_icon, button_bar" /> - - <!-- Guidelines for setting panel border --> - <androidx.constraintlayout.widget.Guideline - android:id="@+id/leftGuideline" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - app:layout_constraintGuide_begin="@dimen/biometric_dialog_border_padding" /> - - <androidx.constraintlayout.widget.Guideline - android:id="@+id/rightGuideline" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="vertical" - app:layout_constraintGuide_end="@dimen/biometric_dialog_border_padding" /> - - <androidx.constraintlayout.widget.Guideline - android:id="@+id/bottomGuideline" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:orientation="horizontal" - app:layout_constraintGuide_end="40dp" /> - - <androidx.constraintlayout.widget.Guideline - android:id="@+id/topGuideline" - android:layout_width="0dp" - android:layout_height="0dp" - android:orientation="horizontal" - app:layout_constraintGuide_begin="56dp" /> - - <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper - android:id="@+id/biometric_icon" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="1.0" - tools:srcCompat="@tools:sample/avatars" /> - - <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper - android:id="@+id/biometric_icon_overlay" - android:layout_width="0dp" - android:layout_height="0dp" - android:layout_gravity="center" - android:contentDescription="@null" - android:scaleType="fitXY" - android:importantForAccessibility="no" - app:layout_constraintBottom_toBottomOf="@+id/biometric_icon" - app:layout_constraintEnd_toEndOf="@+id/biometric_icon" - app:layout_constraintStart_toStartOf="@+id/biometric_icon" - app:layout_constraintTop_toTopOf="@+id/biometric_icon" /> - -</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml b/packages/SystemUI/res/layout/activity_keyboard_shortcut_helper.xml index 292e49610e2a..06d1bf4c01cb 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/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml index 9b5b59fc116f..8d50bfa00fd5 100644 --- a/packages/SystemUI/res/layout/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_one_pane_layout.xml @@ -22,9 +22,11 @@ android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="@id/rightGuideline" + app:layout_constraintEnd_toStartOf="@id/rightGuideline" app:layout_constraintStart_toStartOf="@id/leftGuideline" - app:layout_constraintTop_toTopOf="@id/topBarrier" /> + app:layout_constraintTop_toTopOf="@id/topBarrier" + app:layout_constraintWidth_max="@dimen/biometric_prompt_panel_max_width" /> + <include android:id="@+id/button_bar" @@ -41,8 +43,8 @@ android:layout_height="wrap_content" android:fillViewport="true" android:fadeScrollbars="false" - android:paddingBottom="24dp" - android:paddingHorizontal="24dp" + android:paddingBottom="@dimen/biometric_prompt_top_scroll_view_bottom_padding" + android:paddingHorizontal="@dimen/biometric_prompt_top_scroll_view_horizontal_padding" android:paddingTop="24dp" app:layout_constrainedHeight="true" app:layout_constrainedWidth="true" @@ -76,7 +78,7 @@ style="@style/TextAppearance.AuthCredential.LogoDescription" android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingTop="8dp" + android:paddingTop="@dimen/biometric_prompt_logo_description_top_padding" app:layout_constraintBottom_toTopOf="@+id/title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" @@ -180,14 +182,14 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - app:layout_constraintGuide_begin="0dp" /> + app:layout_constraintGuide_begin="@dimen/biometric_prompt_one_pane_medium_horizontal_guideline_padding" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/rightGuideline" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="vertical" - app:layout_constraintGuide_end="0dp" /> + app:layout_constraintGuide_end="@dimen/biometric_prompt_one_pane_medium_horizontal_guideline_padding" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/bottomGuideline" @@ -201,7 +203,7 @@ android:layout_width="0dp" android:layout_height="0dp" android:orientation="horizontal" - app:layout_constraintGuide_begin="119dp" /> + app:layout_constraintGuide_begin="@dimen/biometric_prompt_one_pane_medium_top_guideline_padding" /> <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper android:id="@+id/biometric_icon" diff --git a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml b/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml index 01b9f7e2e38a..01b9f7e2e38a 100644 --- a/packages/SystemUI/res/layout-land/biometric_prompt_constraint_layout.xml +++ b/packages/SystemUI/res/layout/biometric_prompt_two_pane_layout.xml 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 f45cc7c464d5..000000000000 --- 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 3b9fb3be3814..000000000000 --- 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 a97c90c5e8ac..000000000000 --- 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 2384963c44db..000000000000 --- 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 b0599caae6df..000000000000 --- 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 e3c5a7d03d2e..5f77f61d805b 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 4e25e6d933c4..000000000000 --- 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 56ebc0668097..aea79e84f7e8 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 2cfba01fe1c8..29e0dbea24f2 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) --> @@ -102,6 +97,17 @@ <dimen name="lockscreen_shade_status_bar_transition_distance">@dimen/lockscreen_shade_full_transition_distance</dimen> <dimen name="lockscreen_shade_keyguard_transition_distance">@dimen/lockscreen_shade_media_transition_distance</dimen> + <!-- Dimensions for biometric prompt panel padding --> + <dimen name="biometric_prompt_one_pane_medium_top_guideline_padding">56dp</dimen> + <dimen name="biometric_prompt_one_pane_medium_horizontal_guideline_padding">@dimen/biometric_dialog_border_padding</dimen> + + <!-- Dimensions for biometric prompt scroll view padding --> + <dimen name="biometric_prompt_top_scroll_view_bottom_padding">32dp</dimen> + <dimen name="biometric_prompt_top_scroll_view_horizontal_padding">32dp</dimen> + + <!-- Dimensions for biometric prompt custom content view. --> + <dimen name="biometric_prompt_logo_description_top_padding">16dp</dimen> + <!-- Biometric Auth pattern view size, better to align keyguard_security_width --> <dimen name="biometric_auth_pattern_view_size">348dp</dimen> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index edd3d77555f7..8ce20684d892 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> @@ -1119,11 +1114,18 @@ <dimen name="biometric_dialog_height">240dp</dimen> <!-- Dimensions for biometric prompt panel padding --> - <dimen name="biometric_prompt_small_horizontal_guideline_padding">344dp</dimen> - <dimen name="biometric_prompt_udfps_horizontal_guideline_padding">114dp</dimen> - <dimen name="biometric_prompt_udfps_mid_guideline_padding">409dp</dimen> - <dimen name="biometric_prompt_medium_horizontal_guideline_padding">640dp</dimen> - <dimen name="biometric_prompt_medium_mid_guideline_padding">330dp</dimen> + <dimen name="biometric_prompt_panel_max_width">640dp</dimen> + <dimen name="biometric_prompt_land_small_horizontal_guideline_padding">344dp</dimen> + <dimen name="biometric_prompt_two_pane_udfps_horizontal_guideline_padding">114dp</dimen> + <dimen name="biometric_prompt_two_pane_udfps_mid_guideline_padding">409dp</dimen> + <dimen name="biometric_prompt_two_pane_medium_horizontal_guideline_padding">640dp</dimen> + <dimen name="biometric_prompt_two_pane_medium_mid_guideline_padding">330dp</dimen> + <dimen name="biometric_prompt_one_pane_medium_top_guideline_padding">119dp</dimen> + <dimen name="biometric_prompt_one_pane_medium_horizontal_guideline_padding">0dp</dimen> + + <!-- Dimensions for biometric prompt scroll view padding --> + <dimen name="biometric_prompt_top_scroll_view_bottom_padding">24dp</dimen> + <dimen name="biometric_prompt_top_scroll_view_horizontal_padding">24dp</dimen> <!-- Dimensions for biometric prompt icon padding --> <dimen name="biometric_prompt_portrait_small_bottom_padding">60dp</dimen> @@ -1136,6 +1138,7 @@ <!-- Dimensions for biometric prompt custom content view. --> <dimen name="biometric_prompt_logo_size">32dp</dimen> + <dimen name="biometric_prompt_logo_description_top_padding">8dp</dimen> <dimen name="biometric_prompt_content_corner_radius">28dp</dimen> <dimen name="biometric_prompt_content_padding_horizontal">24dp</dimen> <dimen name="biometric_prompt_content_padding_vertical">16dp</dimen> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index b993a5ad75b9..177ba598add7 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 c038a8207d43..03c7d9b5bbfa 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -895,11 +895,9 @@ <string name="qs_record_issue_dropdown_screenrecord">Screen record</string> <!-- QuickSettings: Issue Type Drop down choices list in Record Issue Start Dialog [CHAR LIMIT=30] --> - <string-array name="qs_record_issue_types"> - <item>Performance</item> - <item>User Interface</item> - <item>Battery</item> - </string-array> + <string name="performance">Performance</string> + <string name="user_interface">User Interface</string> + <string name="thermal">Thermal</string> <!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] --> <string name="quick_settings_onehanded_label">One-handed mode</string> @@ -3489,6 +3487,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 b8f71c10dc89..1e0adec4e84f 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/res/xml/fileprovider.xml b/packages/SystemUI/res/xml/fileprovider.xml index 71cc05ddfefa..78b7e95e8bfa 100644 --- a/packages/SystemUI/res/xml/fileprovider.xml +++ b/packages/SystemUI/res/xml/fileprovider.xml @@ -19,5 +19,4 @@ <cache-path name="leak" path="leak/"/> <external-path name="screenrecord" path="."/> <cache-path name="multi_user" path="multi_user/" /> - <root-path name="traces" path="/data/local/traces"/> </paths> diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt index b99c51489521..44f2059d4e41 100644 --- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt +++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/PromptKind.kt @@ -22,15 +22,28 @@ sealed interface PromptKind { data class Biometric( /** The available modalities for the authentication on the prompt. */ val activeModalities: BiometricModalities = BiometricModalities(), - // TODO(b/330908557): Use this value to decide whether to show two pane layout, instead of - // simply depending on rotations. - val showTwoPane: Boolean = false, - ) : PromptKind + val paneType: PaneType = PaneType.ONE_PANE_PORTRAIT, + ) : PromptKind { + enum class PaneType { + TWO_PANE_LANDSCAPE, + ONE_PANE_PORTRAIT, + ONE_PANE_NO_SENSOR_LANDSCAPE, + ONE_PANE_LARGE_SCREEN_LANDSCAPE + } + } - object Pin : PromptKind - object Pattern : PromptKind - object Password : PromptKind + data object Pin : PromptKind + data object Pattern : PromptKind + data object Password : PromptKind fun isBiometric() = this is Biometric - fun isCredential() = (this is Pin) or (this is Pattern) or (this is Password) + fun isTwoPaneLandscapeBiometric(): Boolean = + (this as? Biometric)?.paneType == Biometric.PaneType.TWO_PANE_LANDSCAPE + fun isOnePanePortraitBiometric() = + (this as? Biometric)?.paneType == Biometric.PaneType.ONE_PANE_PORTRAIT + fun isOnePaneNoSensorLandscapeBiometric() = + (this as? Biometric)?.paneType == Biometric.PaneType.ONE_PANE_NO_SENSOR_LANDSCAPE + fun isOnePaneLargeScreenLandscapeBiometric() = + (this as? Biometric)?.paneType == Biometric.PaneType.ONE_PANE_LARGE_SCREEN_LANDSCAPE + fun isCredential() = (this is Pin) || (this is Pattern) || (this is Password) } 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 1366226a4e24..e8eb53f07833 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 @@ class FlagManager constructor( ) : 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 @@ class FlagManager constructor( 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 @@ class FlagManager constructor( 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 3bf3fb3a1ebd..b116e297ccbf 100644 --- a/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java +++ b/packages/SystemUI/src/com/android/keyguard/CarrierTextManager.java @@ -308,7 +308,13 @@ public class CarrierTextManager { } 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 @@ public class CarrierTextManager { 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 class CarrierTextManager { 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 @@ public class CarrierTextManager { + "carrierText=" + carrierText + ", listOfCarriers=" + Arrays.toString(listOfCarriers) + ", anySimReady=" + anySimReady + + ", isInSatelliteMode=" + isInSatelliteMode + ", subscriptionIds=" + Arrays.toString(subscriptionIds) + ", airplaneMode=" + airplaneMode + '}'; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index 47e4b49e519d..f688d4f17841 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -308,14 +308,14 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mStatusArea = mView.findViewById(R.id.keyguard_status_area); mBgExecutor.execute(() -> { - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK, false, /* notifyForDescendants */ mDoubleLineClockObserver, UserHandle.USER_ALL ); - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED, false, /* notifyForDescendants */ mShowWeatherObserver, @@ -372,8 +372,8 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS setClock(null); mBgExecutor.execute(() -> { - mSecureSettings.unregisterContentObserver(mDoubleLineClockObserver); - mSecureSettings.unregisterContentObserver(mShowWeatherObserver); + mSecureSettings.unregisterContentObserverSync(mDoubleLineClockObserver); + mSecureSettings.unregisterContentObserverSync(mShowWeatherObserver); }); mKeyguardUnlockAnimationController.removeKeyguardUnlockAnimationListener( diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java index ca24ccb3e6ec..f2a68a8d9fd7 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java @@ -319,7 +319,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest } // Unregister observer before removing view - mSecureSettings.unregisterContentObserver(mMagnificationCapabilityObserver); + mSecureSettings.unregisterContentObserverSync(mMagnificationCapabilityObserver); mWindowManager.removeView(mSettingView); mIsVisible = false; if (resetPosition) { @@ -380,7 +380,7 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest mWindowManager.addView(mSettingView, mParams); - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPABILITY, mMagnificationCapabilityObserver, UserHandle.USER_CURRENT); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java index eb840f1f4c90..ffb5f3d47bcc 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java @@ -240,26 +240,26 @@ class MenuInfoRepository { } void registerObserversAndCallbacks() { - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS), /* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver, UserHandle.USER_CURRENT); if (!com.android.systemui.Flags.floatingMenuNarrowTargetContentObserver()) { - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( mSecureSettings.getUriFor(ENABLED_ACCESSIBILITY_SERVICES), /* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver, UserHandle.USER_CURRENT); } - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( mSecureSettings.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE), /* notifyForDescendants */ false, mMenuSizeContentObserver, UserHandle.USER_CURRENT); - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( mSecureSettings.getUriFor(ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED), /* notifyForDescendants */ false, mMenuFadeOutContentObserver, UserHandle.USER_CURRENT); - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( mSecureSettings.getUriFor(ACCESSIBILITY_FLOATING_MENU_OPACITY), /* notifyForDescendants */ false, mMenuFadeOutContentObserver, UserHandle.USER_CURRENT); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt index 91bc0c144773..eaf541d7b559 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegate.kt @@ -29,12 +29,12 @@ import android.widget.SeekBar import android.widget.TextView import androidx.annotation.MainThread import androidx.annotation.WorkerThread -import com.android.systemui.res.R import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener.ControlUnitType import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.util.concurrency.DelayableExecutor @@ -46,7 +46,9 @@ import javax.inject.Inject import kotlin.math.roundToInt /** The Dialog that contains a seekbar for changing the font size. */ -class FontScalingDialogDelegate @Inject constructor( +class FontScalingDialogDelegate +@Inject +constructor( private val context: Context, private val systemUIDialogFactory: SystemUIDialog.Factory, private val layoutInflater: LayoutInflater, @@ -84,9 +86,9 @@ class FontScalingDialogDelegate @Inject constructor( dialog.setTitle(R.string.font_scaling_dialog_title) dialog.setView(layoutInflater.inflate(R.layout.font_scaling_dialog, null)) dialog.setPositiveButton( - R.string.quick_settings_done, - /* onClick = */ null, - /* dismissOnClick = */ true + R.string.quick_settings_done, + /* onClick = */ null, + /* dismissOnClick = */ true ) } @@ -142,7 +144,7 @@ class FontScalingDialogDelegate @Inject constructor( } ) doneButton.setOnClickListener { dialog.dismiss() } - systemSettings.registerContentObserver(Settings.System.FONT_SCALE, fontSizeObserver) + systemSettings.registerContentObserverSync(Settings.System.FONT_SCALE, fontSizeObserver) } /** @@ -165,7 +167,7 @@ class FontScalingDialogDelegate @Inject constructor( override fun onStop(dialog: SystemUIDialog) { cancelUpdateFontScaleRunnable?.run() cancelUpdateFontScaleRunnable = null - systemSettings.unregisterContentObserver(fontSizeObserver) + systemSettings.unregisterContentObserverSync(fontSizeObserver) } @MainThread diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java index 3d7accbd961f..1ee4908437a6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java @@ -29,6 +29,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AlertDialog; import android.content.Context; +import android.content.res.Configuration; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.PixelFormat; @@ -360,15 +361,23 @@ public class AuthContainerView extends LinearLayout Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds), Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds)); + final boolean isLandscape = mContext.getResources().getConfiguration().orientation + == Configuration.ORIENTATION_LANDSCAPE; mPromptSelectorInteractorProvider = promptSelectorInteractorProvider; mPromptSelectorInteractorProvider.get().setPrompt(mConfig.mPromptInfo, mEffectiveUserId, getRequestId(), biometricModalities, mConfig.mOperationId, mConfig.mOpPackageName, - false /*onSwitchToCredential*/); + false /*onSwitchToCredential*/, isLandscape); final LayoutInflater layoutInflater = LayoutInflater.from(mContext); - if (constraintBp() && mPromptViewModel.getPromptKind().getValue().isBiometric()) { - mLayout = (ConstraintLayout) layoutInflater.inflate( - R.layout.biometric_prompt_constraint_layout, this, false /* attachToRoot */); + final PromptKind kind = mPromptViewModel.getPromptKind().getValue(); + if (constraintBp() && kind.isBiometric()) { + if (kind.isTwoPaneLandscapeBiometric()) { + mLayout = (ConstraintLayout) layoutInflater.inflate( + R.layout.biometric_prompt_two_pane_layout, this, false /* attachToRoot */); + } else { + mLayout = (ConstraintLayout) layoutInflater.inflate( + R.layout.biometric_prompt_one_pane_layout, this, false /* attachToRoot */); + } } else { mLayout = (FrameLayout) layoutInflater.inflate( R.layout.auth_container_view, this, false /* attachToRoot */); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt index 8e5a97bd5d8d..9b14d6f68e35 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/DisplayStateRepository.kt @@ -29,11 +29,10 @@ import com.android.systemui.display.data.repository.DeviceStateRepository import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.REAR_DISPLAY import com.android.systemui.display.data.repository.DisplayRepository import javax.inject.Inject +import kotlin.math.min import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -58,7 +57,7 @@ interface DisplayStateRepository { val currentDisplaySize: StateFlow<Size> /** Provides whether the current display is large screen */ - val isLargeScreen: Flow<Boolean> + val isLargeScreen: StateFlow<Boolean> } @SysUISingleton @@ -127,16 +126,29 @@ constructor( ), ) - override val isLargeScreen: Flow<Boolean> = + override val isLargeScreen: StateFlow<Boolean> = currentDisplayInfo .map { - // TODO: This works, but investigate better way to handle this - it.logicalWidth * 160 / it.logicalDensityDpi > DisplayMetrics.DENSITY_XXXHIGH && - it.logicalHeight * 160 / it.logicalDensityDpi > DisplayMetrics.DENSITY_XXHIGH + // copied from systemui/shared/...Utilities.java + val smallestWidth = + dpiFromPx( + min(it.logicalWidth, it.logicalHeight).toFloat(), + context.resources.configuration.densityDpi + ) + smallestWidth >= LARGE_SCREEN_MIN_DPS } - .distinctUntilChanged() + .stateIn( + backgroundScope, + started = SharingStarted.WhileSubscribed(), + initialValue = false, + ) + private fun dpiFromPx(size: Float, densityDpi: Int): Float { + val densityRatio = densityDpi.toFloat() / DisplayMetrics.DENSITY_DEFAULT + return size / densityRatio + } companion object { const val TAG = "DisplayStateRepositoryImpl" + const val LARGE_SCREEN_MIN_DPS = 600f } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt index 68c4a10fcfad..2970890c3fa7 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FaceUserSettingsRepository.kt @@ -75,7 +75,7 @@ private fun SecureSettings.watch( ) { fun fetch(): Boolean = getIntForUser(key, if (defaultValue) 1 else 0, userId) > 0 - registerContentObserverForUser( + registerContentObserverForUserSync( key, false /* notifyForDescendants */, object : ContentObserver(handler) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt index 591da4096956..40313e3158aa 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt @@ -65,7 +65,8 @@ interface DisplayStateInteractor { /** Called on configuration changes, used to keep the display state in sync */ fun onConfigurationChanged(newConfig: Configuration) - val isLargeScreen: Flow<Boolean> + /** Provides whether the current display is large screen */ + val isLargeScreen: StateFlow<Boolean> } /** Encapsulates logic for interacting with the display state. */ @@ -127,7 +128,7 @@ constructor( override val isDefaultDisplayOff = displayRepository.defaultDisplayOff - override val isLargeScreen: Flow<Boolean> = displayStateRepository.isLargeScreen + override val isLargeScreen: StateFlow<Boolean> = displayStateRepository.isLargeScreen companion object { private const val TAG = "DisplayStateInteractor" 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 a74b0b07299c..b8ff3bb43203 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 @@ constructor( ) { 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/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt index dc338d07f9e7..c08756f6ae36 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt @@ -91,6 +91,7 @@ interface PromptSelectorInteractor { challenge: Long, opPackageName: String, onSwitchToCredential: Boolean, + isLandscape: Boolean, ) /** Unset the current authentication request. */ @@ -102,6 +103,7 @@ class PromptSelectorInteractorImpl @Inject constructor( fingerprintPropertyRepository: FingerprintPropertyRepository, + private val displayStateInteractor: DisplayStateInteractor, private val promptRepository: PromptRepository, private val lockPatternUtils: LockPatternUtils, ) : PromptSelectorInteractor { @@ -166,7 +168,9 @@ constructor( modalities, promptRepository.challenge.value!!, promptRepository.opPackageName.value!!, - true /*onSwitchToCredential*/ + onSwitchToCredential = true, + // isLandscape value is not important when onSwitchToCredential is true + isLandscape = false, ) } @@ -178,6 +182,7 @@ constructor( challenge: Long, opPackageName: String, onSwitchToCredential: Boolean, + isLandscape: Boolean, ) { val hasCredentialViewShown = promptKind.value.isCredential() val showBpForCredential = @@ -189,11 +194,30 @@ constructor( !promptInfo.isContentViewMoreOptionsButtonUsed val showBpWithoutIconForCredential = showBpForCredential && !hasCredentialViewShown var kind: PromptKind = PromptKind.None + if (onSwitchToCredential) { kind = getCredentialType(lockPatternUtils, effectiveUserId) } else if (Utils.isBiometricAllowed(promptInfo) || showBpWithoutIconForCredential) { - // TODO(b/330908557): check to show one pane or two pane - kind = PromptKind.Biometric(modalities) + // TODO(b/330908557): Subscribe to + // displayStateInteractor.currentRotation.value.isDefaultOrientation() for checking + // `isLandscape` after removing AuthContinerView. + kind = + if (isLandscape) { + val paneType = + when { + displayStateInteractor.isLargeScreen.value -> + PromptKind.Biometric.PaneType.ONE_PANE_LARGE_SCREEN_LANDSCAPE + showBpWithoutIconForCredential -> + PromptKind.Biometric.PaneType.ONE_PANE_NO_SENSOR_LANDSCAPE + else -> PromptKind.Biometric.PaneType.TWO_PANE_LANDSCAPE + } + PromptKind.Biometric( + modalities, + paneType = paneType, + ) + } else { + PromptKind.Biometric(modalities) + } } else if (isDeviceCredentialAllowed(promptInfo)) { kind = getCredentialType(lockPatternUtils, effectiveUserId) } 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 dddadbd5e036..2f2f3a35dbaa 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/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt index 47174c006735..c836f89a8ff4 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt @@ -93,6 +93,7 @@ object BiometricViewSizeBinder { if (constraintBp()) { val leftGuideline = view.requireViewById<Guideline>(R.id.leftGuideline) + val topGuideline = view.requireViewById<Guideline>(R.id.topGuideline) val rightGuideline = view.requireViewById<Guideline>(R.id.rightGuideline) val midGuideline = view.findViewById<Guideline>(R.id.midGuideline) @@ -355,6 +356,18 @@ object BiometricViewSizeBinder { ) } + if (bounds.top >= 0) { + mediumConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top) + smallConstraintSet.setGuidelineBegin(topGuideline.id, bounds.top) + } else if (bounds.top < 0) { + mediumConstraintSet.setGuidelineEnd( + topGuideline.id, + abs(bounds.top) + ) + smallConstraintSet.setGuidelineEnd(topGuideline.id, abs(bounds.top)) + } + + // Use rect bottom to set mid guideline of two-pane. if (midGuideline != null) { if (bounds.bottom >= 0) { midGuideline.setGuidelineEnd(bounds.bottom) 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 fcc69927c2b3..adf5b7480cb9 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,11 +17,17 @@ package com.android.systemui.biometrics.ui.binder +import android.graphics.drawable.Animatable2 import android.graphics.drawable.AnimatedVectorDrawable +import android.graphics.drawable.Drawable +import android.util.Log +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.airbnb.lottie.LottieAnimationView import com.airbnb.lottie.LottieOnCompositionLoadedListener +import com.airbnb.lottie.LottieListener import com.android.settingslib.widget.LottieColorUtils import com.android.systemui.Flags.constraintBp import com.android.systemui.biometrics.ui.viewmodel.PromptIconViewModel @@ -30,11 +36,14 @@ 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 import kotlinx.coroutines.launch +private const val TAG = "PromptIconViewBinder" + /** Sub-binder for [BiometricPromptLayout.iconView]. */ object PromptIconViewBinder { /** @@ -61,6 +70,16 @@ object PromptIconViewBinder { } 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,17 +145,24 @@ object PromptIconViewBinder { 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, AuthType.Coex -> { + iconView.setIconFailureListener(iconAsset, activeAuthType) iconView.setAnimation(iconAsset) iconView.frame = 0 @@ -145,27 +171,25 @@ object PromptIconViewBinder { } } 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.setIconFailureListener( + iconAsset, + activeAuthType + ) + 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() } } } @@ -188,6 +212,7 @@ object PromptIconViewBinder { ) .collect { (iconOverlayAsset, shouldAnimateIconOverlay, showingError) -> if (iconOverlayAsset != -1) { + iconOverlayView.setIconOverlayFailureListener(iconOverlayAsset) iconOverlayView.setAnimation(iconOverlayAsset) iconOverlayView.frame = 0 LottieColorUtils.applyDynamicColors( @@ -222,3 +247,96 @@ object PromptIconViewBinder { } } } + +private val assetIdToString: Map<Int, String> = + mapOf( + // UDFPS assets + R.raw.fingerprint_dialogue_error_to_fingerprint_lottie to + "fingerprint_dialogue_error_to_fingerprint_lottie", + R.raw.fingerprint_dialogue_error_to_success_lottie to + "fingerprint_dialogue_error_to_success_lottie", + R.raw.fingerprint_dialogue_fingerprint_to_error_lottie to + "fingerprint_dialogue_fingerprint_to_error_lottie", + R.raw.fingerprint_dialogue_fingerprint_to_success_lottie to + "fingerprint_dialogue_fingerprint_to_success_lottie", + // SFPS assets + R.raw.biometricprompt_fingerprint_to_error_landscape to + "biometricprompt_fingerprint_to_error_landscape", + R.raw.biometricprompt_folded_base_bottomright to "biometricprompt_folded_base_bottomright", + R.raw.biometricprompt_folded_base_default to "biometricprompt_folded_base_default", + R.raw.biometricprompt_folded_base_topleft to "biometricprompt_folded_base_topleft", + R.raw.biometricprompt_landscape_base to "biometricprompt_landscape_base", + R.raw.biometricprompt_portrait_base_bottomright to + "biometricprompt_portrait_base_bottomright", + R.raw.biometricprompt_portrait_base_topleft to "biometricprompt_portrait_base_topleft", + R.raw.biometricprompt_symbol_error_to_fingerprint_landscape to + "biometricprompt_symbol_error_to_fingerprint_landscape", + R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_bottomright to + "biometricprompt_symbol_error_to_fingerprint_portrait_bottomright", + R.raw.biometricprompt_symbol_error_to_fingerprint_portrait_topleft to + "biometricprompt_symbol_error_to_fingerprint_portrait_topleft", + R.raw.biometricprompt_symbol_error_to_success_landscape to + "biometricprompt_symbol_error_to_success_landscape", + R.raw.biometricprompt_symbol_error_to_success_portrait_bottomright to + "biometricprompt_symbol_error_to_success_portrait_bottomright", + R.raw.biometricprompt_symbol_error_to_success_portrait_topleft to + "biometricprompt_symbol_error_to_success_portrait_topleft", + R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_bottomright to + "biometricprompt_symbol_fingerprint_to_error_portrait_bottomright", + R.raw.biometricprompt_symbol_fingerprint_to_error_portrait_topleft to + "biometricprompt_symbol_fingerprint_to_error_portrait_topleft", + R.raw.biometricprompt_symbol_fingerprint_to_success_landscape to + "biometricprompt_symbol_fingerprint_to_success_landscape", + R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_bottomright to + "biometricprompt_symbol_fingerprint_to_success_portrait_bottomright", + R.raw.biometricprompt_symbol_fingerprint_to_success_portrait_topleft to + "biometricprompt_symbol_fingerprint_to_success_portrait_topleft", + // Face assets + R.drawable.face_dialog_wink_from_dark to "face_dialog_wink_from_dark", + R.drawable.face_dialog_dark_to_checkmark to "face_dialog_dark_to_checkmark", + R.drawable.face_dialog_pulse_light_to_dark to "face_dialog_pulse_light_to_dark", + R.drawable.face_dialog_pulse_dark_to_light to "face_dialog_pulse_dark_to_light", + R.drawable.face_dialog_dark_to_error to "face_dialog_dark_to_error", + R.drawable.face_dialog_error_to_idle to "face_dialog_error_to_idle", + R.drawable.face_dialog_idle_static to "face_dialog_idle_static", + // Co-ex assets + R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie to + "fingerprint_dialogue_unlocked_to_checkmark_success_lottie", + R.raw.fingerprint_dialogue_error_to_unlock_lottie to + "fingerprint_dialogue_error_to_unlock_lottie", + R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie to + "fingerprint_dialogue_fingerprint_to_unlock_lottie", + ) + +private fun getAssetNameFromId(id: Int): String { + return assetIdToString.getOrDefault(id, "Asset $id not found") +} + +private fun LottieAnimationView.setIconFailureListener(iconAsset: Int, activeAuthType: AuthType) { + setFailureListener( + LottieListener<Throwable> { result: Throwable? -> + Log.d( + TAG, + "Collecting iconAsset | " + + "activeAuthType = $activeAuthType | " + + "Invalid resource id: $iconAsset, " + + "name ${getAssetNameFromId(iconAsset)}", + result + ) + } + ) +} + +private fun LottieAnimationView.setIconOverlayFailureListener(iconOverlayAsset: Int) { + setFailureListener( + LottieListener<Throwable> { result: Throwable? -> + Log.d( + TAG, + "Collecting iconOverlayAsset | " + + "Invalid resource id: $iconOverlayAsset, " + + "name ${getAssetNameFromId(iconOverlayAsset)}", + result + ) + } + ) +} 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 901d7517c5e9..bde3e992a295 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.DrawableRes 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.res.R 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 @@ constructor( } /** - * 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 @@ constructor( _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 @@ constructor( } } 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 @@ constructor( } 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 @@ constructor( } } + /** 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/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt index 156ec6b975a5..c17b83dd4fbe 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt @@ -261,10 +261,13 @@ constructor( combine( _forceLargeSize, displayStateInteractor.isLargeScreen, - displayStateInteractor.currentRotation + displayStateInteractor.currentRotation, ) { forceLarge, isLargeScreen, rotation -> when { - forceLarge || isLargeScreen -> PromptPosition.Bottom + forceLarge || + isLargeScreen || + promptKind.value.isOnePaneNoSensorLandscapeBiometric() -> + PromptPosition.Bottom rotation == DisplayRotation.ROTATION_90 -> PromptPosition.Right rotation == DisplayRotation.ROTATION_270 -> PromptPosition.Left rotation == DisplayRotation.ROTATION_180 -> PromptPosition.Top @@ -297,23 +300,27 @@ constructor( /** Prompt panel size padding */ private val smallHorizontalGuidelinePadding = context.resources.getDimensionPixelSize( - R.dimen.biometric_prompt_small_horizontal_guideline_padding + R.dimen.biometric_prompt_land_small_horizontal_guideline_padding ) private val udfpsHorizontalGuidelinePadding = context.resources.getDimensionPixelSize( - R.dimen.biometric_prompt_udfps_horizontal_guideline_padding + R.dimen.biometric_prompt_two_pane_udfps_horizontal_guideline_padding ) private val udfpsMidGuidelinePadding = context.resources.getDimensionPixelSize( - R.dimen.biometric_prompt_udfps_mid_guideline_padding + R.dimen.biometric_prompt_two_pane_udfps_mid_guideline_padding + ) + private val mediumTopGuidelinePadding = + context.resources.getDimensionPixelSize( + R.dimen.biometric_prompt_one_pane_medium_top_guideline_padding ) private val mediumHorizontalGuidelinePadding = context.resources.getDimensionPixelSize( - R.dimen.biometric_prompt_medium_horizontal_guideline_padding + R.dimen.biometric_prompt_two_pane_medium_horizontal_guideline_padding ) private val mediumMidGuidelinePadding = context.resources.getDimensionPixelSize( - R.dimen.biometric_prompt_medium_mid_guideline_padding + R.dimen.biometric_prompt_two_pane_medium_mid_guideline_padding ) /** Rect for positioning biometric icon */ @@ -416,9 +423,9 @@ constructor( * asset to be loaded before determining the prompt size. */ val isIconViewLoaded: Flow<Boolean> = - combine(modalities, _isIconViewLoaded.asStateFlow()) { modalities, isIconViewLoaded -> - val noIcon = modalities.isEmpty - noIcon || isIconViewLoaded + combine(hideSensorIcon, _isIconViewLoaded.asStateFlow()) { hideSensorIcon, isIconViewLoaded + -> + hideSensorIcon || isIconViewLoaded } .distinctUntilChanged() @@ -448,17 +455,24 @@ constructor( * from opposite side of the screen */ val guidelineBounds: Flow<Rect> = - combine(iconPosition, size, position, modalities) { _, size, position, modalities -> + combine(iconPosition, promptKind, size, position, modalities) { + _, + promptKind, + size, + position, + modalities -> when (position) { - PromptPosition.Bottom -> Rect(0, 0, 0, 0) + PromptPosition.Bottom -> + if (promptKind.isOnePaneNoSensorLandscapeBiometric()) { + Rect(0, 0, 0, 0) + } else { + Rect(0, mediumTopGuidelinePadding, 0, 0) + } PromptPosition.Right -> if (size.isSmall) { Rect(-smallHorizontalGuidelinePadding, 0, 0, 0) } else if (modalities.hasUdfps) { Rect(udfpsHorizontalGuidelinePadding, 0, 0, udfpsMidGuidelinePadding) - } else if (modalities.isEmpty) { - // TODO: Temporary fix until no biometric landscape layout is added - Rect(-mediumHorizontalGuidelinePadding, 0, 0, 6) } else { Rect(-mediumHorizontalGuidelinePadding, 0, 0, mediumMidGuidelinePadding) } @@ -467,9 +481,6 @@ constructor( Rect(0, 0, -smallHorizontalGuidelinePadding, 0) } else if (modalities.hasUdfps) { Rect(0, 0, udfpsHorizontalGuidelinePadding, -udfpsMidGuidelinePadding) - } else if (modalities.isEmpty) { - // TODO: Temporary fix until no biometric landscape layout is added - Rect(0, 0, -mediumHorizontalGuidelinePadding, -6) } else { Rect( 0, 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 2b9fc73458d8..7a9429e56c88 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.BrightnessPolicyRepositor 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 @@ interface ScreenBrightnessModule { @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/repository/ScreenBrightnessRepository.kt b/packages/SystemUI/src/com/android/systemui/brightness/data/repository/ScreenBrightnessRepository.kt index 9ed11d13d4d4..37d1887730b9 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 @@ package com.android.systemui.brightness.data.repository 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 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 @@ class ScreenBrightnessDisplayManagerRepository 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 @@ constructor( displayManager.setBrightness(displayId, value) } } + logBrightnessChange(call is SetBrightnessMethod.Permanent, value) } } } @@ -147,13 +156,15 @@ constructor( 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 @@ constructor( 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 @@ constructor( @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 799a0a14c99d..5647f521762f 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 @@ class ScreenBrightnessInteractor @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 @@ constructor( * 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 @@ constructor( 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/model/BrightnessLog.kt index e20d003bb989..b514fefbff0e 100644 --- a/packages/SystemUI/src/com/android/systemui/brightness/shared/GammaBrightness.kt +++ b/packages/SystemUI/src/com/android/systemui/brightness/shared/model/BrightnessLog.kt @@ -14,16 +14,11 @@ * limitations under the License. */ -package com.android.systemui.brightness.shared +package com.android.systemui.brightness.shared.model -import androidx.annotation.IntRange -import com.android.settingslib.display.BrightnessUtils +import javax.inject.Qualifier -@JvmInline -value class GammaBrightness( - @IntRange( - from = BrightnessUtils.GAMMA_SPACE_MIN.toLong(), - to = BrightnessUtils.GAMMA_SPACE_MAX.toLong() - ) - val value: Int -) +@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 000000000000..7eba6268869c --- /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 000000000000..1c886e6b1477 --- /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 a51d8ff4faa5..f991d5b8405f 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.res.stringResource 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 @@ fun BrightnessSliderContainer( 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 f0988ba96bcd..16a1dcc0aef5 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 @@ package com.android.systemui.brightness.ui.viewmodel 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 @@ class BrightnessSliderViewModel 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 153b7aa3e522..8993a3be058b 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 @@ package com.android.systemui.communal 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.keyguard.shared.model.KeyguardState 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 @@ constructor( if ( finishedState == KeyguardState.GLANCEABLE_HUB && !dreaming && + !glanceableHubAllowKeyguardWhenDreaming() && dreamManager.canStartDreaming(isAwake) ) { dreamManager.startDream() diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt index d6d08b4f1208..260dcbad6201 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSceneRepository.kt @@ -22,6 +22,7 @@ import com.android.compose.animation.scene.TransitionKey import com.android.systemui.communal.dagger.Communal import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.scene.shared.model.SceneDataSource import javax.inject.Inject @@ -34,6 +35,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch /** Encapsulates the state of communal mode. */ interface CommunalSceneRepository { @@ -64,6 +66,7 @@ interface CommunalSceneRepository { class CommunalSceneRepositoryImpl @Inject constructor( + @Application private val applicationScope: CoroutineScope, @Background backgroundScope: CoroutineScope, @Communal private val sceneDataSource: SceneDataSource, ) : CommunalSceneRepository { @@ -82,11 +85,19 @@ constructor( ) override fun changeScene(toScene: SceneKey, transitionKey: TransitionKey?) { - sceneDataSource.changeScene(toScene, transitionKey) + applicationScope.launch { + // SceneTransitionLayout state updates must be triggered on the thread the STL was + // created on. + sceneDataSource.changeScene(toScene, transitionKey) + } } override fun snapToScene(toScene: SceneKey) { - sceneDataSource.snapToScene(toScene) + applicationScope.launch { + // SceneTransitionLayout state updates must be triggered on the thread the STL was + // created on. + sceneDataSource.snapToScene(toScene) + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt index 88cb64c95c04..1c47e507c972 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt @@ -30,6 +30,7 @@ import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_D import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_FLAG import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_INVALID_USER import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING +import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.flags.FeatureFlagsClassic @@ -59,6 +60,9 @@ interface CommunalSettingsRepository { /** Keyguard widgets enabled state by Device Policy Manager for the specified user. */ fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> + + /** The type of background to use for the hub. Used to experiment with different backgrounds. */ + fun getBackground(user: UserInfo): Flow<CommunalBackgroundType> } @SysUISingleton @@ -126,6 +130,21 @@ constructor( .emitOnStart() .map { devicePolicyManager.areKeyguardWidgetsAllowed(user.id) } + override fun getBackground(user: UserInfo): Flow<CommunalBackgroundType> = + secureSettings + .observerFlow(userId = user.id, names = arrayOf(GLANCEABLE_HUB_BACKGROUND_SETTING)) + .emitOnStart() + .map { + val intType = + secureSettings.getIntForUser( + GLANCEABLE_HUB_BACKGROUND_SETTING, + CommunalBackgroundType.DEFAULT.value, + user.id + ) + CommunalBackgroundType.entries.find { type -> type.value == intType } + ?: CommunalBackgroundType.DEFAULT + } + private fun getEnabledByUser(user: UserInfo): Flow<Boolean> = secureSettings .observerFlow(userId = user.id, names = arrayOf(Settings.Secure.GLANCEABLE_HUB_ENABLED)) @@ -141,6 +160,7 @@ constructor( companion object { const val GLANCEABLE_HUB_CONTENT_SETTING = "glanceable_hub_content_setting" + const val GLANCEABLE_HUB_BACKGROUND_SETTING = "glanceable_hub_background" private const val ENABLED_SETTING_DEFAULT = 1 } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt index 3e5126a307eb..f043d58543fc 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt @@ -21,6 +21,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.communal.data.model.CommunalEnabledState import com.android.systemui.communal.data.model.CommunalWidgetCategories import com.android.systemui.communal.data.repository.CommunalSettingsRepository +import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.dagger.CommunalTableLog @@ -30,6 +31,7 @@ import com.android.systemui.settings.UserTracker import com.android.systemui.user.domain.interactor.SelectedUserInteractor import java.util.concurrent.Executor import javax.inject.Inject +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.channels.awaitClose @@ -38,6 +40,7 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -47,6 +50,7 @@ class CommunalSettingsInteractor @Inject constructor( @Background private val bgScope: CoroutineScope, + @Background private val bgDispatcher: CoroutineDispatcher, @Background private val bgExecutor: Executor, private val repository: CommunalSettingsRepository, userInteractor: SelectedUserInteractor, @@ -78,6 +82,12 @@ constructor( initialValue = CommunalWidgetCategories.defaultCategories ) + /** The type of background to use for the hub. Used to experiment with different backgrounds */ + val communalBackground: Flow<CommunalBackgroundType> = + userInteractor.selectedUserInfo + .flatMapLatest { user -> repository.getBackground(user) } + .flowOn(bgDispatcher) + private val workProfileUserInfoCallbackFlow: Flow<UserInfo?> = conflatedCallbackFlow { fun send(profiles: List<UserInfo>) { trySend(profiles.find { it.isManagedProfile }) diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt index b35b7f5debb3..8b816db53970 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamActivityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalBackgroundType.kt @@ -13,15 +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 +package com.android.systemui.communal.shared.model -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? +/** Models the types of background that can be shown on the hub. */ +enum class CommunalBackgroundType(val value: Int) { + DEFAULT(0), + STATIC_GRADIENT(1), + ANIMATED(2), } 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 3d9e8615fb18..8cd5603bdc7f 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 @@ abstract class BaseCommunalViewModel( /** 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 7f3a2dcb23dc..3e00b0455575 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 @@ -21,8 +21,10 @@ import android.view.View import android.view.accessibility.AccessibilityNodeInfo import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor +import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.communal.domain.interactor.CommunalTutorialInteractor import com.android.systemui.communal.domain.model.CommunalContentModel +import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main @@ -38,7 +40,9 @@ import com.android.systemui.media.controls.ui.view.MediaHostState 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 @@ -68,6 +72,7 @@ constructor( keyguardInteractor: KeyguardInteractor, communalSceneInteractor: CommunalSceneInteractor, private val communalInteractor: CommunalInteractor, + private val communalSettingsInteractor: CommunalSettingsInteractor, tutorialInteractor: CommunalTutorialInteractor, private val shadeInteractor: ShadeInteractor, @Named(MediaModule.COMMUNAL_HUB) mediaHost: MediaHost, @@ -76,8 +81,11 @@ constructor( 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 +101,40 @@ constructor( } } .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() } @@ -248,6 +287,10 @@ constructor( */ val showGestureIndicator: Flow<Boolean> = not(keyguardInteractor.isDreaming) + /** The type of background to use for the hub. */ + val communalBackground: Flow<CommunalBackgroundType> = + communalSettingsInteractor.communalBackground + companion object { const val POPUP_AUTO_HIDE_TIMEOUT_MS = 12000L } @@ -255,5 +298,6 @@ constructor( sealed class PopupType { object CtaTile : PopupType() + object CustomizeWidgetButton : PopupType() } diff --git a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java index 0bdc7f1dfb96..84807fbc8ece 100644 --- a/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java +++ b/packages/SystemUI/src/com/android/systemui/complication/ComplicationTypesUpdater.java @@ -69,15 +69,15 @@ public class ComplicationTypesUpdater extends ConditionalCoreStartable { } }; - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.SCREENSAVER_COMPLICATIONS_ENABLED, settingsObserver, UserHandle.myUserId()); - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.SCREENSAVER_HOME_CONTROLS_ENABLED, settingsObserver, UserHandle.myUserId()); - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, settingsObserver, UserHandle.myUserId()); diff --git a/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java b/packages/SystemUI/src/com/android/systemui/complication/OpenHubComplication.java index 3cf22b1b55e4..a679bfb15476 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 @@ public class OpenHubComplication implements Complication { @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/doze/DozeSensors.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java index 319494252786..7ae840938fe7 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSensors.java @@ -426,7 +426,7 @@ public class DozeSensors { } if (!anyListening) { - mSecureSettings.unregisterContentObserver(mSettingsObserver); + mSecureSettings.unregisterContentObserverSync(mSettingsObserver); } else if (!mSettingRegistered) { for (TriggerSensor s : mTriggerSensors) { s.registerSettingsObserver(mSettingsObserver); @@ -750,7 +750,7 @@ public class DozeSensors { public void registerSettingsObserver(ContentObserver settingsObserver) { if (mConfigured && !TextUtils.isEmpty(mSetting)) { - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( mSetting, mSettingsObserver, UserHandle.USER_ALL); } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 96e708fca451..c6c5747973b3 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 @@ package com.android.systemui.dreams; 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 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ 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 b0d134f5f15f..f6ac7a579140 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.DreamOverlayNotificationCountProvider; 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 @@ public interface DreamModule { } - /** 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/DreamServiceDelegate.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/DreamServiceDelegate.kt new file mode 100644 index 000000000000..2cfb02eadd19 --- /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 index 0854e939645b..7dc5434c595e 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.app.Activity 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 76187c614b5d..77c54ec1eac3 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.log.dagger.DreamLog 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 @@ constructor( 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 @@ constructor( override fun onAttachedToWindow() { super.onAttachedToWindow() - val activity = dreamActivityProvider.getActivity(this) + val activity = dreamServiceDelegate.getActivity(this) if (activity == null) { finish() return @@ -79,9 +80,9 @@ constructor( taskFragmentFactory .create( activity = activity, - onCreateCallback = this::onTaskFragmentCreated, + onCreateCallback = { launchActivity() }, onInfoChangedCallback = this::onTaskFragmentInfoChanged, - hide = { endDream() } + hide = { endDream(false) } ) .apply { createTaskFragment() } @@ -91,16 +92,24 @@ constructor( 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 @@ constructor( * 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 c5b3c5335fc8..4b07f78eb825 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 @@ constructor( 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 c08434015ab1..3d3584e29def 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 @@ object Flags { "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 @@ object Flags { 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 @@ object Flags { @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/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java index 49be03cb08ad..1e4fb4f15062 100644 --- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java +++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java @@ -428,7 +428,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene // get notified of phone state changes mTelephonyListenerManager.addServiceStateListener(mPhoneStateListener); - mGlobalSettings.registerContentObserver( + mGlobalSettings.registerContentObserverSync( Settings.Global.getUriFor(Settings.Global.AIRPLANE_MODE_ON), true, mAirplaneModeObserver); mHasVibrator = vibrator.hasVibrator(); @@ -453,7 +453,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene public void destroy() { mBroadcastDispatcher.unregisterReceiver(mBroadcastReceiver); mTelephonyListenerManager.removeServiceStateListener(mPhoneStateListener); - mGlobalSettings.unregisterContentObserver(mAirplaneModeObserver); + mGlobalSettings.unregisterContentObserverSync(mAirplaneModeObserver); mConfigurationController.removeCallback(this); } 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 ea8d7d778851..30b958393b60 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 @@ package com.android.systemui.haptics.qs 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( } 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 @@ constructor( when (state) { State.TIMEOUT_WAIT -> { setState(State.IDLE) + clearActionType() } State.RUNNING_FORWARD -> { _postedActionType.value = ActionType.REVERSE_ANIMATOR @@ -145,18 +146,23 @@ constructor( /** 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 @@ constructor( 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 @@ constructor( /* 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 4875f481cce6..92a55ef0e74f 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.app.tracing.coroutines.launch 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 @@ object QSLongPressEffectViewBinder { ): 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 @@ object QSLongPressEffectViewBinder { 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 @@ object QSLongPressEffectViewBinder { } } } - - @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/PhysicalKeyboardCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt index f16a3bdfb975..90867edd8236 100644 --- a/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/keyboard/PhysicalKeyboardCoreStartable.kt @@ -19,10 +19,12 @@ package com.android.systemui.keyboard import android.hardware.input.InputSettings import com.android.systemui.CoreStartable +import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.flags.FeatureFlags -import com.android.systemui.flags.Flags +import com.android.systemui.flags.Flags as LegacyFlag import com.android.systemui.keyboard.backlight.ui.KeyboardBacklightDialogCoordinator +import com.android.systemui.keyboard.docking.binder.KeyboardDockingIndicationViewBinder import com.android.systemui.keyboard.stickykeys.ui.StickyKeysIndicatorCoordinator import dagger.Lazy import javax.inject.Inject @@ -34,14 +36,18 @@ class PhysicalKeyboardCoreStartable constructor( private val keyboardBacklightDialogCoordinator: Lazy<KeyboardBacklightDialogCoordinator>, private val stickyKeysIndicatorCoordinator: Lazy<StickyKeysIndicatorCoordinator>, + private val keyboardDockingIndicationViewBinder: Lazy<KeyboardDockingIndicationViewBinder>, private val featureFlags: FeatureFlags, ) : CoreStartable { override fun start() { - if (featureFlags.isEnabled(Flags.KEYBOARD_BACKLIGHT_INDICATOR)) { + if (featureFlags.isEnabled(LegacyFlag.KEYBOARD_BACKLIGHT_INDICATOR)) { keyboardBacklightDialogCoordinator.get().startListening() } if (InputSettings.isAccessibilityStickyKeysFeatureEnabled()) { stickyKeysIndicatorCoordinator.get().startListening() } + if (Flags.keyboardDockingIndicator()) { + keyboardDockingIndicationViewBinder.get().startListening() + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt new file mode 100644 index 000000000000..f649be2432c6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/binder/KeyboardDockingIndicationViewBinder.kt @@ -0,0 +1,100 @@ +/* + * 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.docking.binder + +import android.content.Context +import android.graphics.Paint +import android.graphics.PixelFormat +import android.view.WindowManager +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.keyboard.docking.ui.KeyboardDockingIndicationView +import com.android.systemui.keyboard.docking.ui.viewmodel.KeyboardDockingIndicationViewModel +import com.android.systemui.surfaceeffects.PaintDrawCallback +import com.android.systemui.surfaceeffects.glowboxeffect.GlowBoxEffect +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@SysUISingleton +class KeyboardDockingIndicationViewBinder +@Inject +constructor( + context: Context, + @Application private val applicationScope: CoroutineScope, + private val viewModel: KeyboardDockingIndicationViewModel, + private val windowManager: WindowManager +) { + + private val windowLayoutParams = + WindowManager.LayoutParams().apply { + width = WindowManager.LayoutParams.MATCH_PARENT + height = WindowManager.LayoutParams.MATCH_PARENT + format = PixelFormat.TRANSLUCENT + type = WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG + fitInsetsTypes = 0 // Ignore insets from all system bars + title = "Edge glow effect" + flags = + (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) + setTrustedOverlay() + } + + private var glowEffect: GlowBoxEffect? = null + private val glowEffectView = KeyboardDockingIndicationView(context, null) + + private val drawCallback = + object : PaintDrawCallback { + override fun onDraw(paint: Paint) { + glowEffectView.draw(paint) + } + } + + private val stateChangedCallback = + object : GlowBoxEffect.AnimationStateChangedCallback { + override fun onStart() { + windowManager.addView(glowEffectView, windowLayoutParams) + } + + override fun onEnd() { + windowManager.removeView(glowEffectView) + } + } + + fun startListening() { + applicationScope.launch { + viewModel.edgeGlow.collect { config -> + if (glowEffect == null) { + glowEffect = GlowBoxEffect(config, drawCallback, stateChangedCallback) + } else { + glowEffect?.finish(force = true) + glowEffect!!.updateConfig(config) + } + } + } + + applicationScope.launch { + viewModel.keyboardConnected.collect { connected -> + if (connected) { + glowEffect?.play() + } else { + glowEffect?.finish() + } + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/domain/interactor/KeyboardDockingIndicationInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/domain/interactor/KeyboardDockingIndicationInteractor.kt new file mode 100644 index 000000000000..c670b5ec73f1 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/domain/interactor/KeyboardDockingIndicationInteractor.kt @@ -0,0 +1,29 @@ +/* + * 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.docking.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyboard.data.repository.KeyboardRepository +import javax.inject.Inject + +/** Listens for keyboard docking event. */ +@SysUISingleton +class KeyboardDockingIndicationInteractor +@Inject +constructor(keyboardRepository: KeyboardRepository) { + val onKeyboardConnected = keyboardRepository.isAnyKeyboardConnected +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/KeyboardDockingIndicationView.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/KeyboardDockingIndicationView.kt new file mode 100644 index 000000000000..de8b2cf8663f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/KeyboardDockingIndicationView.kt @@ -0,0 +1,42 @@ +/* + * 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.docking.ui + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View + +/** View that's used for rendering keyboard docking indicator. */ +class KeyboardDockingIndicationView(context: Context?, attrs: AttributeSet?) : + View(context, attrs) { + + private var paint: Paint? = null + + override fun onDraw(canvas: Canvas) { + if (!canvas.isHardwareAccelerated) { + return + } + paint?.let { canvas.drawPaint(it) } + } + + fun draw(paint: Paint) { + this.paint = paint + invalidate() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt new file mode 100644 index 000000000000..2578b785056d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModel.kt @@ -0,0 +1,142 @@ +/* + * 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.docking.ui.viewmodel + +import android.content.Context +import android.view.Surface +import android.view.WindowManager +import com.android.settingslib.Utils +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor +import com.android.systemui.surfaceeffects.glowboxeffect.GlowBoxConfig +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch + +@SysUISingleton +class KeyboardDockingIndicationViewModel +@Inject +constructor( + private val windowManager: WindowManager, + private val context: Context, + keyboardDockingIndicationInteractor: KeyboardDockingIndicationInteractor, + configurationInteractor: ConfigurationInteractor, + @Background private val backgroundScope: CoroutineScope, +) { + + private val _edgeGlow: MutableStateFlow<GlowBoxConfig> = MutableStateFlow(createEffectConfig()) + val edgeGlow = _edgeGlow.asStateFlow() + val keyboardConnected = keyboardDockingIndicationInteractor.onKeyboardConnected + + init { + /** + * Expected behaviors: + * 1) On keyboard docking event, we play the animation for a fixed duration. + * 2) If the keyboard gets disconnected during the animation, we finish the animation with + * ease out. + * 3) If the configuration changes (e.g., device rotation), we force cancel the animation + * with no ease out. + */ + backgroundScope.launch { + configurationInteractor.onAnyConfigurationChange.collect { + _edgeGlow.value = createEffectConfig() + } + } + } + + private fun createEffectConfig(): GlowBoxConfig { + val bounds = windowManager.currentWindowMetrics.bounds + val width = bounds.width().toFloat() + val height = bounds.height().toFloat() + + val startCenterX: Float + val startCenterY: Float + val endCenterX: Float + val endCenterY: Float + val boxWidth: Float + val boxHeight: Float + + when (context.display.rotation) { + Surface.ROTATION_0 -> { + endCenterX = width + endCenterY = height * 0.5f + startCenterX = endCenterX + OFFSET + startCenterY = endCenterY + boxWidth = THICKNESS + boxHeight = height + } + Surface.ROTATION_90 -> { + endCenterX = width * 0.5f + endCenterY = 0f + startCenterX = endCenterX + startCenterY = endCenterY - OFFSET + boxWidth = width + boxHeight = THICKNESS + } + Surface.ROTATION_180 -> { + endCenterX = 0f + endCenterY = height * 0.5f + startCenterX = endCenterX - OFFSET + startCenterY = endCenterY + boxWidth = THICKNESS + boxHeight = height + } + Surface.ROTATION_270 -> { + endCenterX = width * 0.5f + endCenterY = height + startCenterX = endCenterX + startCenterY = endCenterY + OFFSET + boxWidth = width + boxHeight = THICKNESS + } + else -> { // Shouldn't happen. Just fall off to ROTATION_0 + endCenterX = width + endCenterY = height * 0.5f + startCenterX = endCenterX + OFFSET + startCenterY = endCenterY + boxWidth = THICKNESS + boxHeight = height + } + } + + return GlowBoxConfig( + startCenterX = startCenterX, + startCenterY = startCenterY, + endCenterX = endCenterX, + endCenterY = endCenterY, + width = boxWidth, + height = boxHeight, + color = Utils.getColorAttr(context, android.R.attr.colorAccent).defaultColor, + blurAmount = BLUR_AMOUNT, + duration = DURATION, + easeInDuration = EASE_DURATION, + easeOutDuration = EASE_DURATION + ) + } + + private companion object { + private const val OFFSET = 300f + private const val THICKNESS = 20f + private const val BLUR_AMOUNT = 700f + private const val DURATION = 3000L + private const val EASE_DURATION = 800L + } +} 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 000000000000..52ccc219353e --- /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 ef4156da4f7b..1e8d23918964 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 android.view.WindowInsets 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 81c2d92d29e8..f3a1843b6c31 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 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } 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 3cbcb2cb4a0b..97ea16d7f8c8 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 @@ package com.android.systemui.keyguard 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 com.android.systemui.utils.GlobalWindowManager 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 @@ import kotlinx.coroutines.launch 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 @@ constructor( // 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/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt index a2bbcadbf8c7..f15896ba8dcf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt @@ -183,7 +183,7 @@ interface KeyguardRepository { val statusBarState: StateFlow<StatusBarState> /** Observable for biometric unlock state which includes the mode and unlock source */ - val biometricUnlockState: Flow<BiometricUnlockModel> + val biometricUnlockState: StateFlow<BiometricUnlockModel> fun setBiometricUnlockState( unlockMode: BiometricUnlockMode, @@ -307,17 +307,20 @@ constructor( private val _dismissAction: MutableStateFlow<DismissAction> = MutableStateFlow(DismissAction.None) override val dismissAction = _dismissAction.asStateFlow() + override fun setDismissAction(dismissAction: DismissAction) { _dismissAction.value = dismissAction } private val _keyguardDone: MutableSharedFlow<KeyguardDone> = MutableSharedFlow() override val keyguardDone = _keyguardDone.asSharedFlow() + override suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone) { _keyguardDone.emit(keyguardDoneType) } override val keyguardDoneAnimationsFinished: MutableSharedFlow<Unit> = MutableSharedFlow() + override fun keyguardDoneAnimationsFinished() { keyguardDoneAnimationsFinished.tryEmit(Unit) } @@ -490,6 +493,7 @@ constructor( override fun onStartDream() { trySendWithFailureLogging(true, TAG, "updated isDreamingWithOverlay") } + override fun onWakeUp() { trySendWithFailureLogging(false, TAG, "updated isDreamingWithOverlay") } @@ -618,7 +622,8 @@ constructor( private val _biometricUnlockState: MutableStateFlow<BiometricUnlockModel> = MutableStateFlow(BiometricUnlockModel(BiometricUnlockMode.NONE, null)) - override val biometricUnlockState = _biometricUnlockState.asStateFlow() + override val biometricUnlockState: StateFlow<BiometricUnlockModel> = + _biometricUnlockState.asStateFlow() override fun setBiometricUnlockState( unlockMode: BiometricUnlockMode, diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index f488d3b67fc7..8ec460a7088f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -265,7 +265,7 @@ constructor( state: TransitionState ) { if (updateTransitionId != transitionId) { - Log.w(TAG, "Attempting to update with old/invalid transitionId: $transitionId") + Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId") return } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt index 576fafdc903e..ebc348388b40 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractor.kt @@ -3,6 +3,7 @@ package com.android.systemui.keyguard.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.BiometricUnlockMode +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_DISMISS_BOUNCER import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_NONE @@ -15,6 +16,7 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_ import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.StateFlow @ExperimentalCoroutinesApi @SysUISingleton @@ -24,6 +26,8 @@ constructor( private val keyguardRepository: KeyguardRepository, ) { + val unlockState: StateFlow<BiometricUnlockModel> = keyguardRepository.biometricUnlockState + fun setBiometricUnlockState( @WakeAndUnlockMode unlockStateInt: Int, biometricUnlockSource: BiometricUnlockSource?, 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 9b07675f672c..756c6c20e58d 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 @@ constructor( @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 @@ constructor( 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 01109af79c1d..2a9ee9fb8779 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 @@ constructor( @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 @@ constructor( 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 7d3de306d621..f5e98f1fedfe 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 @@ constructor( @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 @@ constructor( 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 63294f7609a2..47aa02a0be52 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 @@ constructor( @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 @@ constructor( 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 7961b45830d4..25c3b0d395c0 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 @@ constructor( @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 @@ constructor( 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 ca6ab3ef52d8..e516fa3c44bb 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 @@ constructor( @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 @@ constructor( 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 8ca29c80c2e9..a540d761c38f 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 @@ constructor( @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 @@ constructor( 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 f1e98f3bbe6d..8cab3cd35dcf 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 @@ constructor( @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 @@ constructor( 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 2603aab2781b..86d4cfb916ed 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 @@ constructor( @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 @@ constructor( 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 76a822369b0c..19b2b81c4b27 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 @@ constructor( @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 @@ constructor( 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 c44a40f33857..73835a3c1c96 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.CameraLaunchSourceModel 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.flag.SceneContainerFlag 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.combineTransform 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 @@ constructor( // 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( @@ -139,6 +181,8 @@ constructor( /** Doze transition information. */ val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel + val isPulsing: Flow<Boolean> = dozeTransitionModel.map { it.to == DozeStateModel.DOZE_PULSING } + /** * Whether the system is dreaming. [isDreaming] will be always be true when [isDozing] is true, * but not vice-versa. 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 323ceef06a97..e14820714c9b 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 @@ sealed class TransitionInteractor( 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 @@ sealed class TransitionInteractor( @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 6d579f3b2513..f5b82cfc5191 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 @@ interface KeyguardBlueprint { 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 @@ interface KeyguardBlueprint { it.bindData(constraintLayout) } } + rebuildSections.forEach { it.onRebuildEnd() } } /** Rebuilds views for the target sections, or all of them if unspecified. */ @@ -61,6 +63,7 @@ interface KeyguardBlueprint { return } + rebuildSections.forEach { it.onRebuildBegin() } rebuildSections.forEach { it.removeViews(constraintLayout) } rebuildSections.forEach { it.addViews(constraintLayout) @@ -68,6 +71,7 @@ interface KeyguardBlueprint { 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 48a214615733..473b08f7debc 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 @@ abstract class KeyguardSection { /** 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/view/layout/sections/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index c5fab8f57822..e01f0a152b37 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 @@ constructor( 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 2d6690fbbc99..0435531bb87c 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 @@ constructor( 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 198e9f26384a..940f42377b42 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 @@ constructor( } .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), + started = SharingStarted.Eagerly, initialValue = ClockSize.LARGE, ) @@ -74,7 +74,7 @@ constructor( .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 8409f15dca81..448a71c36a99 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.MigrateClocksToBlueprint 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 @@ constructor( private val burnInInteractor: BurnInInteractor, private val shortcutsCombinedViewModel: KeyguardQuickAffordancesCombinedViewModel, configurationInteractor: ConfigurationInteractor, + keyguardTransitionInteractor: KeyguardTransitionInteractor, ) { /** Notifies when a new configuration is set */ @@ -69,12 +73,22 @@ constructor( .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/media/controls/domain/MediaDomainModule.kt b/packages/SystemUI/src/com/android/systemui/media/controls/domain/MediaDomainModule.kt index e0c54190283a..9c29bab80d14 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 @@ interface MediaDomainModule { @IntoMap @ClassKey(MediaDataProcessor::class) fun bindMediaDataProcessor(interactor: MediaDataProcessor): CoreStartable + companion object { @Provides @@ -52,7 +53,7 @@ interface MediaDomainModule { 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 eed775242d1f..8e985e11732f 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 @@ class MediaDataProcessor( } override fun start() { - if (!mediaFlags.isMediaControlsRefactorEnabled()) { + if (!mediaFlags.isSceneContainerEnabled()) { return } @@ -746,8 +746,7 @@ class MediaDataProcessor( 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 9e6230012760..b4bd4fd2c266 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.MediaSessionBasedFilt 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 @@ constructor( val currentMedia: StateFlow<List<MediaCommonModel>> = mediaFilterRepository.currentMedia override fun start() { - if (!mediaFlags.isMediaControlsRefactorEnabled()) { + if (!mediaFlags.isSceneContainerEnabled()) { return } @@ -256,8 +256,6 @@ constructor( 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 19e3e0715989..edead51dd8e0 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.KeyguardUpdateMonitor 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 @@ private val DEBUG = Log.isLoggable(TAG, Log.DEBUG) class MediaCarouselController @Inject constructor( + @Application applicationScope: CoroutineScope, private val context: Context, private val mediaControlPanelFactory: Provider<MediaControlPanel>, private val visualStabilityProvider: VisualStabilityProvider, @@ -217,7 +219,7 @@ constructor( 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 @@ constructor( inflateSettingsButton() mediaContent = mediaCarousel.requireViewById(R.id.media_carousel) configurationController.addCallback(configListener) - if (!mediaFlags.isMediaControlsRefactorEnabled()) { + if (!mediaFlags.isSceneContainerEnabled()) { setUpListeners() } else { val visualStabilityCallback = OnReorderingAllowedListener { @@ -387,15 +389,15 @@ constructor( 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( + globalSettings.registerContentObserverSync( Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE), animationScaleObserver ) @@ -882,8 +884,7 @@ constructor( 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 @@ constructor( } private fun updatePlayers(recreateMedia: Boolean) { - if (mediaFlags.isMediaControlsRefactorEnabled()) { + if (mediaFlags.isSceneContainerEnabled()) { updateMediaPlayers(recreateMedia) return } @@ -1192,7 +1193,7 @@ constructor( 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 @@ constructor( /** 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 @@ constructor( 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 @@ constructor( !mediaManager.hasActiveMediaOrRecommendation() && desiredHostState.showsOnlyActiveMedia - if (!mediaFlags.isMediaControlsRefactorEnabled()) { + if (!mediaFlags.isSceneContainerEnabled()) { for (mediaPlayer in MediaPlayerData.players()) { if (animate) { mediaPlayer.mediaViewController.animatePendingStateChange( @@ -1401,7 +1402,7 @@ constructor( } 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 @@ constructor( @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 a4f3e2174791..601d563ffc45 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.Application 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.animation.UniqueObjectHostView 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 @@ val View.isShownNotFaded: Boolean * 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 @@ constructor( private val mediaManager: MediaDataManager, private val keyguardViewController: KeyguardViewController, private val dreamOverlayStateController: DreamOverlayStateController, + private val keyguardInteractor: KeyguardInteractor, communalTransitionViewModel: CommunalTransitionViewModel, configurationController: ConfigurationController, wakefulnessLifecycle: WakefulnessLifecycle, @@ -159,6 +167,7 @@ constructor( private var targetBounds: Rect = Rect() private val mediaFrame get() = mediaCarouselController.mediaFrame + private var statusbarState: Int = statusBarStateController.state private var animator = ValueAnimator.ofFloat(0.0f, 1.0f).apply { @@ -236,6 +245,15 @@ constructor( 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 +431,12 @@ constructor( /** 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 @@ -577,7 +601,7 @@ constructor( } } } - secureSettings.registerContentObserverForUser( + secureSettings.registerContentObserverForUserSync( Settings.Secure.MEDIA_CONTROLS_LOCK_SCREEN, settingsObserver, UserHandle.USER_ALL @@ -585,11 +609,26 @@ constructor( // 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 +844,9 @@ constructor( 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 +989,9 @@ constructor( @VisibleForTesting @TransformationType fun calculateTransformationType(): Int { + if (isHubTransition) { + return TRANSFORMATION_TYPE_FADE + } if (isTransitioningToFullShade) { if (inSplitShade && areGuidedTransitionHostsVisible()) { return TRANSFORMATION_TYPE_TRANSITION @@ -977,7 +1022,7 @@ constructor( * otherwise */ private fun getTransformationProgress(): Float { - if (skipQqsOnExpansion) { + if (skipQqsOnExpansion || isHubTransition) { return -1.0f } val progress = getQSTransformationProgress() @@ -1147,15 +1192,18 @@ constructor( } 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 +1238,9 @@ constructor( // 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 +1339,9 @@ constructor( * 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 38377088a2d7..9d0723211d4b 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( * @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 @@ constructor( ) ) } - if (mediaFlags.isMediaControlsRefactorEnabled()) { + if (mediaFlags.isSceneContainerEnabled()) { if ( this@MediaViewController::recsConfigurationChangeListener.isInitialized ) { @@ -305,6 +305,7 @@ constructor( */ 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 @@ constructor( * 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 @@ constructor( } fun attachPlayer(mediaViewHolder: MediaViewHolder) { - if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!mediaFlags.isSceneContainerEnabled()) return this.mediaViewHolder = mediaViewHolder // Setting up seek bar. @@ -731,7 +732,7 @@ constructor( } 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 @@ constructor( } fun attachRecommendations(recommendationViewHolder: RecommendationViewHolder) { - if (!mediaFlags.isMediaControlsRefactorEnabled()) return + if (!mediaFlags.isSceneContainerEnabled()) return this.recommendationViewHolder = recommendationViewHolder attach(recommendationViewHolder.recommendations, TYPE.RECOMMENDATION) @@ -796,13 +797,13 @@ constructor( } 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 @@ constructor( } 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/MediaFlags.kt b/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaFlags.kt index 1e7bc0cacf1d..21c311191710 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 @@ class MediaFlags @Inject constructor(private val featureFlags: FeatureFlagsClass /** 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 a256b59ac076..e931f8f5398a 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 @@ import com.android.systemui.navigationbar.gestural.BackPanelController.DelayedOn 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 @@ class BackPanel( // 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 @@ class BackPanel( */ 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 @@ class BackPanel( */ 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 @@ class BackPanel( horizontalTranslation, arrowAlpha, backgroundAlpha - ) + ) /** * Canvas vertical translation. How far up/down the arrow and background appear relative to the @@ -140,43 +142,45 @@ class BackPanel( */ 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 @@ class BackPanel( } 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 @@ class BackPanel( 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 @@ class BackPanel( } fun addAnimationEndListener( - animatedFloat: AnimatedFloat, - endListener: DelayedOnAnimationEndListener + animatedFloat: AnimatedFloat, + endListener: DelayedOnAnimationEndListener ): Boolean { return if (animatedFloat.isRunning) { animatedFloat.addEndListener(endListener) @@ -314,51 +319,51 @@ class BackPanel( } 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 @@ class BackPanel( } 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 @@ class BackPanel( 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 @@ class BackPanel( 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 @@ class BackPanel( 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 @@ class BackPanel( // 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 @@ class BackPanel( } 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 @@ class BackPanel( } 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 f8086f5f6fb4..18358a79cbca 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.Gravity 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 androidx.dynamicanimation.animation.DynamicAnimation 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 @@ internal constructor( 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 @@ internal constructor( 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 @@ internal constructor( ) { /** 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 @@ internal constructor( private val elapsedTimeSinceInactive get() = systemClock.uptimeMillis() - gestureInactiveTime + private val elapsedTimeSinceEntry get() = systemClock.uptimeMillis() - gestureEntryTime @@ -612,6 +612,7 @@ internal constructor( } private var previousPreThresholdWidthInterpolator = params.entryWidthInterpolator + private fun preThresholdWidthStretchAmount(progress: Float): Float { val interpolator = run { val isPastSlop = totalTouchDeltaInactive > viewConfiguration.scaledTouchSlop @@ -677,8 +678,7 @@ internal constructor( 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 @@ internal constructor( 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 @@ internal constructor( 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 d0f8412c85b2..41cd2c46b998 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.Rect; 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.ArraySet; 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 androidx.annotation.DimenRes; 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.SysUiStatsLog; 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 class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack Provider<LightBarController> lightBarControllerProvider) { mContext = context; mDisplayId = context.getDisplayId(); - mMainExecutor = executor; - mMainHandler = handler; + mUiThreadContext = uiThreadContext; mBackgroundExecutor = backgroundExecutor; mUserTracker = userTracker; mOverviewProxyService = overviewProxyService; @@ -478,7 +472,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack ViewConfiguration.getLongPressTimeout()); mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver( - mMainHandler, mContext, this::onNavigationSettingsChanged); + mUiThreadContext.getHandler(), mContext, this::onNavigationSettingsChanged); updateCurrentUserResources(); } @@ -506,11 +500,13 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } private void updateIsEnabled() { + mUiThreadContext.runWithScissors(this::updateIsEnabledInner); + } + + private void updateIsEnabledInner() { try { Trace.beginSection("EdgeBackGestureHandler#updateIsEnabled"); @@ -661,12 +663,12 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack // 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack } 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack updateBackAnimationThresholds(); if (mLightBarControllerProvider.get() != null) { mBackAnimation.setStatusBarCustomizer((appearance) -> { - mMainExecutor.execute(() -> + mUiThreadContext.getExecutor().execute(() -> mLightBarControllerProvider.get() .customizeStatusBarAppearance(appearance)); }); @@ -1308,8 +1311,7 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack 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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack @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 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack /** 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 439b7e18e0df..db8749f59d9c 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 @@ import com.android.systemui.res.R 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 @@ data class EdgePanelParams(private var resources: Resources) { } 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 - ) - - entryIndicator = BackIndicatorDimens( + 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( 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 @@ data class EdgePanelParams(private var resources: Resources) { 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 deb0fed0ffc8..954e94af1c1a 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.ViewModelProvider 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 @@ import kotlinx.coroutines.launch /** 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 @@ constructor( } // 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 59c76adb721b..000000000000 --- 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 10a2b3ce7b85..000000000000 --- 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/qrcodescanner/controller/QRCodeScannerController.java b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java index 2d460a0d28fb..765b45bdbf2e 100644 --- a/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java +++ b/packages/SystemUI/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerController.java @@ -341,7 +341,7 @@ public class QRCodeScannerController implements } mQRCodeScannerPreferenceObserver.forEach((key, value) -> { - mSecureSettings.unregisterContentObserver(value); + mSecureSettings.unregisterContentObserverSync(value); }); // Reset cached values to default as we are no longer listening @@ -418,7 +418,7 @@ public class QRCodeScannerController implements }); } }); - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( mSecureSettings.getUriFor(LOCK_SCREEN_SHOW_QR_CODE_SCANNER), false, mQRCodeScannerPreferenceObserver.get(userId), userId); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt index 2fafba1f188a..e4bafcd9fdb3 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt @@ -52,7 +52,9 @@ private const val DELIMITER = "," * * It also handles restore gracefully. */ -class AutoAddTracker @VisibleForTesting constructor( +class AutoAddTracker +@VisibleForTesting +constructor( private val secureSettings: SecureSettings, private val broadcastDispatcher: BroadcastDispatcher, private val qsHost: QSHost, @@ -66,39 +68,43 @@ class AutoAddTracker @VisibleForTesting constructor( private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED) } - @GuardedBy("autoAdded") - private val autoAdded = ArraySet<String>() + @GuardedBy("autoAdded") private val autoAdded = ArraySet<String>() private var restoredTiles: Map<String, AutoTile>? = null override val currentUserId: Int get() = userId - private val contentObserver = object : ContentObserver(mainHandler) { - override fun onChange( - selfChange: Boolean, - uris: Collection<Uri>, - flags: Int, - _userId: Int - ) { - if (_userId != userId) { - // Ignore changes outside of our user. We'll load the correct value on user change - return + private val contentObserver = + object : ContentObserver(mainHandler) { + override fun onChange( + selfChange: Boolean, + uris: Collection<Uri>, + flags: Int, + _userId: Int + ) { + if (_userId != userId) { + // Ignore changes outside of our user. We'll load the correct value on user + // change + return + } + loadTiles() } - loadTiles() } - } - private val restoreReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - if (intent.action != Intent.ACTION_SETTING_RESTORED) return - processRestoreIntent(intent) + private val restoreReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != Intent.ACTION_SETTING_RESTORED) return + processRestoreIntent(intent) + } } - } private fun processRestoreIntent(intent: Intent) { when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) { Settings.Secure.QS_TILES -> { - restoredTiles = intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) + restoredTiles = + intent + .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) ?.split(DELIMITER) ?.mapIndexed(::AutoTile) ?.associateBy(AutoTile::tileType) @@ -109,13 +115,11 @@ class AutoAddTracker @VisibleForTesting constructor( } Settings.Secure.QS_AUTO_ADDED_TILES -> { restoredTiles?.let { restoredTiles -> - val restoredAutoAdded = intent - .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE) - ?.split(DELIMITER) + val restoredAutoAdded = + intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)?.split(DELIMITER) ?: emptyList() - val autoAddedBeforeRestore = intent - .getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE) - ?.split(DELIMITER) + val autoAddedBeforeRestore = + intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)?.split(DELIMITER) ?: emptyList() val tilesToRemove = restoredAutoAdded.filter { it !in restoredTiles } @@ -123,50 +127,51 @@ class AutoAddTracker @VisibleForTesting constructor( Log.d(TAG, "Removing tiles: $tilesToRemove") qsHost.removeTiles(tilesToRemove) } - val tiles = synchronized(autoAdded) { - autoAdded.clear() - autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore) - getTilesFromListLocked() - } + val tiles = + synchronized(autoAdded) { + autoAdded.clear() + autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore) + getTilesFromListLocked() + } saveTiles(tiles) - } ?: run { - Log.w(TAG, "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " + - "${Settings.Secure.QS_TILES} for user $userId") } + ?: run { + Log.w( + TAG, + "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " + + "${Settings.Secure.QS_TILES} for user $userId" + ) + } } else -> {} // Do nothing for other Settings } } - /** - * Init method must be called after construction to start listening - */ + /** Init method must be called after construction to start listening */ fun initialize() { dumpManager.registerDumpable(TAG, this) loadTiles() - secureSettings.registerContentObserverForUser( - secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES), - contentObserver, - UserHandle.USER_ALL + secureSettings.registerContentObserverForUserSync( + secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES), + contentObserver, + UserHandle.USER_ALL ) registerBroadcastReceiver() } - /** - * Unregister listeners, receivers and observers - */ + /** Unregister listeners, receivers and observers */ fun destroy() { dumpManager.unregisterDumpable(TAG) - secureSettings.unregisterContentObserver(contentObserver) + secureSettings.unregisterContentObserverSync(contentObserver) unregisterBroadcastReceiver() } private fun registerBroadcastReceiver() { broadcastDispatcher.registerReceiver( - restoreReceiver, - FILTER, - backgroundExecutor, - UserHandle.of(userId) + restoreReceiver, + FILTER, + backgroundExecutor, + UserHandle.of(userId) ) } @@ -186,13 +191,9 @@ class AutoAddTracker @VisibleForTesting constructor( fun getRestoredTilePosition(tile: String): Int = restoredTiles?.get(tile)?.index ?: QSHost.POSITION_AT_END - /** - * Returns `true` if the tile has been auto-added before - */ + /** Returns `true` if the tile has been auto-added before */ fun isAdded(tile: String): Boolean { - return synchronized(autoAdded) { - tile in autoAdded - } + return synchronized(autoAdded) { tile in autoAdded } } /** @@ -201,13 +202,14 @@ class AutoAddTracker @VisibleForTesting constructor( * From here on, [isAdded] will return true for that tile. */ fun setTileAdded(tile: String) { - val tiles = synchronized(autoAdded) { - if (autoAdded.add(tile)) { - getTilesFromListLocked() - } else { - null + val tiles = + synchronized(autoAdded) { + if (autoAdded.add(tile)) { + getTilesFromListLocked() + } else { + null + } } - } tiles?.let { saveTiles(it) } } @@ -217,13 +219,14 @@ class AutoAddTracker @VisibleForTesting constructor( * This allows for this tile to be auto-added again in the future. */ fun setTileRemoved(tile: String) { - val tiles = synchronized(autoAdded) { - if (autoAdded.remove(tile)) { - getTilesFromListLocked() - } else { - null + val tiles = + synchronized(autoAdded) { + if (autoAdded.remove(tile)) { + getTilesFromListLocked() + } else { + null + } } - } tiles?.let { saveTiles(it) } } @@ -233,12 +236,12 @@ class AutoAddTracker @VisibleForTesting constructor( private fun saveTiles(tiles: String) { secureSettings.putStringForUser( - Settings.Secure.QS_AUTO_ADDED_TILES, - tiles, - /* tag */ null, - /* makeDefault */ false, - userId, - /* overrideableByRestore */ true + Settings.Secure.QS_AUTO_ADDED_TILES, + tiles, + /* tag */ null, + /* makeDefault */ false, + userId, + /* overrideableByRestore */ true ) } @@ -261,7 +264,9 @@ class AutoAddTracker @VisibleForTesting constructor( } @SysUISingleton - class Builder @Inject constructor( + class Builder + @Inject + constructor( private val secureSettings: SecureSettings, private val broadcastDispatcher: BroadcastDispatcher, private val qsHost: QSHost, @@ -278,16 +283,16 @@ class AutoAddTracker @VisibleForTesting constructor( fun build(): AutoAddTracker { return AutoAddTracker( - secureSettings, - broadcastDispatcher, - qsHost, - dumpManager, - handler, - executor, - userId + secureSettings, + broadcastDispatcher, + qsHost, + dumpManager, + handler, + executor, + userId ) } } private data class AutoTile(val index: Int, val tileType: String) -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 4ee2db796aef..cc0901fca822 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 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { } 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 e4249757d737..38d7290fc3bc 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 @@ public class QSFragmentLegacy extends LifecycleFragment implements QS { } @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 1f4838e85e79..8c0d122e5c00 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.IndentingPrintWriter; 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.keyguard.BouncerPanelExpansionCalculator; 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 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl 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 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl 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 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl 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 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl } 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 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl } @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 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl 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 @@ public class QSImpl implements QS, CommandQueue.Callbacks, StatusBarStateControl 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/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 828d6ed6ab89..03c2aa6f4bc4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -108,7 +108,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P private AutoTileManager mAutoTiles; private final ArrayList<QSFactory> mQsFactories = new ArrayList<>(); private int mCurrentUser; - private final ShadeController mShadeController; + private final Lazy<ShadeController> mShadeControllerProvider; private Context mUserContext; private UserTracker mUserTracker; private SecureSettings mSecureSettings; @@ -130,7 +130,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P PluginManager pluginManager, TunerService tunerService, Provider<AutoTileManager> autoTiles, - ShadeController shadeController, + Lazy<ShadeController> shadeControllerProvider, QSLogger qsLogger, UserTracker userTracker, SecureSettings secureSettings, @@ -149,7 +149,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P mUserFileManager = userFileManager; mFeatureFlags = featureFlags; - mShadeController = shadeController; + mShadeControllerProvider = shadeControllerProvider; if (featureFlags.getTilesEnabled()) { mQsFactories.add(newQsTileFactoryProvider.get()); @@ -216,17 +216,17 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P @Override public void collapsePanels() { - mShadeController.postAnimateCollapseShade(); + mShadeControllerProvider.get().postAnimateCollapseShade(); } @Override public void forceCollapsePanels() { - mShadeController.postAnimateForceCollapseShade(); + mShadeControllerProvider.get().postAnimateForceCollapseShade(); } @Override public void openPanels() { - mShadeController.postAnimateExpandQs(); + mShadeControllerProvider.get().postAnimateExpandQs(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt b/packages/SystemUI/src/com/android/systemui/qs/QSUtils.kt index 15c3f271469d..5482e6da9a57 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 @@ object QSUtils { } @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/ReduceBrightColorsControllerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java index 4fc660948926..846d63f10875 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/ReduceBrightColorsControllerImpl.java @@ -77,8 +77,8 @@ public class ReduceBrightColorsControllerImpl implements public void onUserChanged(int newUser, Context userContext) { synchronized (mListeners) { if (mListeners.size() > 0) { - mSecureSettings.unregisterContentObserver(mContentObserver); - mSecureSettings.registerContentObserverForUser( + mSecureSettings.unregisterContentObserverSync(mContentObserver); + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, false, mContentObserver, newUser); } @@ -94,7 +94,7 @@ public class ReduceBrightColorsControllerImpl implements if (!mListeners.contains(listener)) { mListeners.add(listener); if (mListeners.size() == 1) { - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, false, mContentObserver, mUserTracker.getUserId()); } @@ -106,7 +106,7 @@ public class ReduceBrightColorsControllerImpl implements public void removeCallback(@androidx.annotation.NonNull Listener listener) { synchronized (mListeners) { if (mListeners.remove(listener) && mListeners.size() == 0) { - mSecureSettings.unregisterContentObserver(mContentObserver); + mSecureSettings.unregisterContentObserverSync(mContentObserver); } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java index eb11568614b7..6092348b964e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java +++ b/packages/SystemUI/src/com/android/systemui/qs/SettingObserver.java @@ -74,10 +74,10 @@ public abstract class SettingObserver extends ContentObserver implements Listena mListening = listening; if (listening) { mObservedValue = getValueFromProvider(); - mSettingsProxy.registerContentObserver( + mSettingsProxy.registerContentObserverSync( mSettingsProxy.getUriFor(mSettingName), false, this); } else { - mSettingsProxy.unregisterContentObserver(this); + mSettingsProxy.unregisterContentObserverSync(this); mObservedValue = mDefaultValue; } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java index 539c2d6e64da..1b34c33c9ca0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java +++ b/packages/SystemUI/src/com/android/systemui/qs/UserSettingObserver.java @@ -76,10 +76,10 @@ public abstract class UserSettingObserver extends ContentObserver implements Lis mListening = listening; if (listening) { mObservedValue = getValueFromProvider(); - mSettingsProxy.registerContentObserverForUser( + mSettingsProxy.registerContentObserverForUserSync( mSettingsProxy.getUriFor(mSettingName), false, this, mUserId); } else { - mSettingsProxy.unregisterContentObserver(this); + mSettingsProxy.unregisterContentObserverSync(this); mObservedValue = mDefaultValue; } } 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 0995dd4e592e..000000000000 --- 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 e581bfceb18f..095bdf2ff5bd 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 @@ package com.android.systemui.qs.panels.data.repository 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( + override fun isIconTile(spec: TileSpec): Boolean { + return !LARGE_TILES.contains(spec) + } + + companion object { + private val LARGE_TILES = 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("internet"), + TileSpec.create("bt"), TileSpec.create("dnd"), - TileSpec.create("flashlight"), - TileSpec.create("location"), - TileSpec.create("mictoggle"), - TileSpec.create("nfc"), - TileSpec.create("night"), - TileSpec.create("rotation") + TileSpec.create("cast"), ) - ) - - /** Set of toggleable tiles that are suitable for being shown as an icon. */ - override val iconTilesSpecs: StateFlow<Set<TileSpec>> = _iconTilesSpecs.asStateFlow() + } } 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 ccc1c6e9135c..524ea8b70100 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.dagger.SysUISingleton 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 b437f645d4bc..e99c64c8c1f3 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 @@ constructor( 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 4aeaa7d23771..2f0fe221a2b4 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 @@ constructor( 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 @@ constructor( ) { 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 @@ constructor( 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 708ef0dd7ff6..d60076745a78 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.material3.Text 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 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition 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 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition 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 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition 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 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition iconTileHeight = iconTileHeight, tilePadding = tilePadding, onRemoveTile = onRemoveTile, - isIconOnly = isIconOnly, + isIconOnly = viewModel::isIconTile, columns = columns, showLabels = showLabels, ) @@ -161,7 +156,7 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition iconTileHeight = iconTileHeight, tilePadding = tilePadding, addTileToEnd = addTileToEnd, - isIconOnly = isIconOnly, + isIconOnly = viewModel::isIconTile, showLabels = showLabels, columns = columns, ) @@ -232,7 +227,7 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition 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 @@ class PartitionedGridLayout @Inject constructor(private val viewModel: Partition ) 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 70d629fa7c70..7f4e0a7047b8 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 @@ constructor( // 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 @@ constructor( 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 @@ constructor( 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 a6838c0c06a2..f776bf08c9e4 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 @@ fun TileLazyGrid( @Composable fun DefaultEditTileGrid( tiles: List<EditTileViewModel>, - iconOnlySpecs: Set<TileSpec>, + isIconOnly: (TileSpec) -> Boolean, columns: GridCells, modifier: Modifier, onAddTile: (TileSpec, Int) -> Unit, @@ -176,8 +176,6 @@ fun DefaultEditTileGrid( 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 9ad00c8d3cfa..117c85c9c3ba 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.dagger.SysUISingleton 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 214e9f097642..24b80b8b069a 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 @@ constructor( 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/UserAutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt index d452241e86e2..9fcb0db0c88d 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/UserAutoAddRepository.kt @@ -44,9 +44,8 @@ constructor( @Background private val bgDispatcher: CoroutineDispatcher, ) { - private val changeEvents = MutableSharedFlow<ChangeAction>( - extraBufferCapacity = CHANGES_BUFFER_SIZE - ) + private val changeEvents = + MutableSharedFlow<ChangeAction>(extraBufferCapacity = CHANGES_BUFFER_SIZE) private lateinit var _autoAdded: StateFlow<Set<TileSpec>> @@ -85,8 +84,8 @@ constructor( trySend(Unit) } } - secureSettings.registerContentObserverForUser(SETTING, observer, userId) - awaitClose { secureSettings.unregisterContentObserver(observer) } + secureSettings.registerContentObserverForUserSync(SETTING, observer, userId) + awaitClose { secureSettings.unregisterContentObserverSync(observer) } } .map { load() } .flowOn(bgDispatcher) 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 8ad5cb2c0a34..1f9570a5722e 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 @@ constructor( _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) @@ -92,8 +98,8 @@ constructor( trySend(Unit) } } - secureSettings.registerContentObserverForUser(SETTING, observer, userId) - awaitClose { secureSettings.unregisterContentObserver(observer) } + secureSettings.registerContentObserverForUserSync(SETTING, observer, userId) + awaitClose { secureSettings.unregisterContentObserverSync(observer) } } .map { loadTilesFromSettings(userId) } .flowOn(backgroundDispatcher) 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 b7fcef4376ea..97b5e87d7167 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.TileSpec 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 @@ constructor( 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 @@ constructor( installedTilesComponentRepository.getInstalledTilesComponents(it) } + private val minTiles: Int + get() = + if (retailModeRepository.inRetailMode) { + 1 + } else { + minimumTilesRepository.minNumberOfTiles + } + init { if (featureFlags.pipelineEnabled) { startTileCollection() @@ -273,7 +283,7 @@ constructor( 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 c6dfdd5c137b..762dacdfdddb 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 @@ package com.android.systemui.qs.tileimpl 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.Log 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 @@ open class QSTileViewImpl @JvmOverloads constructor( 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 @@ open class QSTileViewImpl @JvmOverloads constructor( 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 @@ open class QSTileViewImpl @JvmOverloads constructor( 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 @@ open class QSTileViewImpl @JvmOverloads constructor( // 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/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt index 9937ea468fa8..e6801022ad0e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt @@ -46,11 +46,14 @@ import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.recordissue.IssueRecordingService import com.android.systemui.recordissue.IssueRecordingState import com.android.systemui.recordissue.RecordIssueDialogDelegate +import com.android.systemui.recordissue.TraceurMessageSender import com.android.systemui.res.R import com.android.systemui.screenrecord.RecordingService import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.traceur.TraceUtils.PresetTraceType +import java.util.concurrent.Executor import javax.inject.Inject class RecordIssueTile @@ -70,6 +73,8 @@ constructor( private val dialogTransitionAnimator: DialogTransitionAnimator, private val panelInteractor: PanelInteractor, private val userContextProvider: UserContextProvider, + private val traceurMessageSender: TraceurMessageSender, + @Background private val bgExecutor: Executor, private val issueRecordingState: IssueRecordingState, private val delegateFactory: RecordIssueDialogDelegate.Factory, ) : @@ -96,6 +101,11 @@ constructor( } } + override fun handleDestroy() { + super.handleDestroy() + bgExecutor.execute { traceurMessageSender.unbindFromTraceur(mContext) } + } + override fun getTileLabel(): CharSequence = mContext.getString(R.string.qs_record_issue_label) /** @@ -121,14 +131,14 @@ constructor( } } - private fun startIssueRecordingService(screenRecord: Boolean, winscopeTracing: Boolean) = + private fun startIssueRecordingService(screenRecord: Boolean, traceType: PresetTraceType) = PendingIntent.getForegroundService( userContextProvider.userContext, RecordingService.REQUEST_CODE, IssueRecordingService.getStartIntent( userContextProvider.userContext, screenRecord, - winscopeTracing + traceType ), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) @@ -147,7 +157,7 @@ constructor( val dialog: AlertDialog = delegateFactory .create { - startIssueRecordingService(it.screenRecord, it.winscopeTracing) + startIssueRecordingService(it.screenRecord, it.traceType) dialogTransitionAnimator.disableAllCurrentDialogsExitAnimations() panelInteractor.collapsePanels() } diff --git a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt index d48d55dd9918..c1a56465064f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsShadeSceneViewModel.kt @@ -21,8 +21,12 @@ import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.UserAction import com.android.compose.animation.scene.UserActionResult +import com.android.systemui.brightness.ui.viewmodel.BrightnessSliderViewModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.qs.panels.ui.viewmodel.EditModeViewModel +import com.android.systemui.qs.panels.ui.viewmodel.TileGridViewModel +import com.android.systemui.qs.ui.adapter.QSSceneAdapter import com.android.systemui.shade.ui.viewmodel.OverlayShadeViewModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -37,7 +41,11 @@ class QuickSettingsShadeSceneViewModel @Inject constructor( @Application private val applicationScope: CoroutineScope, - overlayShadeViewModel: OverlayShadeViewModel, + val overlayShadeViewModel: OverlayShadeViewModel, + val brightnessSliderViewModel: BrightnessSliderViewModel, + val tileGridViewModel: TileGridViewModel, + val editModeViewModel: EditModeViewModel, + val qsSceneAdapter: QSSceneAdapter, ) { val destinationScenes: StateFlow<Map<UserAction, UserActionResult>> = overlayShadeViewModel.backgroundScene diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt index bb3b65409a47..23dbc26d40d3 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingConfig.kt @@ -16,4 +16,6 @@ package com.android.systemui.recordissue -data class IssueRecordingConfig(val screenRecord: Boolean, val winscopeTracing: Boolean) +import com.android.traceur.TraceUtils.PresetTraceType + +data class IssueRecordingConfig(val screenRecord: Boolean, val traceType: PresetTraceType) diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt index 6694878cea74..5ede64a4fc26 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/IssueRecordingService.kt @@ -24,8 +24,6 @@ import android.content.res.Resources import android.net.Uri import android.os.Handler import android.os.UserHandle -import android.util.Log -import androidx.core.content.FileProvider import com.android.internal.logging.UiEventLogger import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.dagger.qualifiers.LongRunning @@ -37,16 +35,10 @@ import com.android.systemui.screenrecord.RecordingService import com.android.systemui.screenrecord.RecordingServiceStrings import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil -import com.android.traceur.FileSender -import com.android.traceur.TraceUtils -import java.io.File -import java.io.FileOutputStream -import java.nio.file.Files +import com.android.traceur.MessageConstants.INTENT_EXTRA_TRACE_TYPE +import com.android.traceur.TraceUtils.PresetTraceType import java.util.concurrent.Executor -import java.util.zip.ZipEntry -import java.util.zip.ZipOutputStream import javax.inject.Inject -import kotlin.jvm.optionals.getOrElse class IssueRecordingService @Inject @@ -60,6 +52,7 @@ constructor( keyguardDismissUtil: KeyguardDismissUtil, private val dialogTransitionAnimator: DialogTransitionAnimator, private val panelInteractor: PanelInteractor, + private val traceurMessageSender: TraceurMessageSender, private val issueRecordingState: IssueRecordingState, private val iActivityManager: IActivityManager, ) : @@ -82,17 +75,14 @@ constructor( override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.action) { ACTION_START -> { - TraceUtils.traceStart( - this, - DEFAULT_TRACE_TAGS, - DEFAULT_BUFFER_SIZE, - DEFAULT_IS_INCLUDING_WINSCOPE, - DEFAULT_IS_INCLUDING_APP_TRACE, - DEFAULT_IS_LONG_TRACE, - DEFAULT_ATTACH_TO_BUGREPORT, - DEFAULT_MAX_TRACE_SIZE, - DEFAULT_MAX_TRACE_DURATION_IN_MINUTES - ) + bgExecutor.execute { + traceurMessageSender.startTracing( + intent.getSerializableExtra( + INTENT_EXTRA_TRACE_TYPE, + PresetTraceType::class.java + ) + ) + } issueRecordingState.isRecording = true if (!intent.getBooleanExtra(EXTRA_SCREEN_RECORD, false)) { // If we don't want to record the screen, the ACTION_SHOW_START_NOTIF action @@ -102,7 +92,10 @@ constructor( } ACTION_STOP, ACTION_STOP_NOTIF -> { - TraceUtils.traceStop(this) + // ViewCapture needs to save it's data before it is disabled, or else the data will + // be lost. This is expected to change in the near future, and when that happens + // this line should be removed. + bgExecutor.execute { traceurMessageSender.stopTracing() } issueRecordingState.isRecording = false } ACTION_SHARE -> { @@ -117,7 +110,7 @@ constructor( if (issueRecordingState.takeBugReport) { iActivityManager.requestBugReportWithExtraAttachment(screenRecording) } else { - shareRecording(screenRecording) + traceurMessageSender.shareTraces(applicationContext, screenRecording) } } @@ -134,75 +127,10 @@ constructor( return super.onStartCommand(intent, flags, startId) } - private fun shareRecording(screenRecording: Uri?) { - val traces = - TraceUtils.traceDump(this, TRACE_FILE_NAME).getOrElse { - Log.v( - TAG, - "Traces were not present. This can happen if users double" + - "click on share notification. Traces are cleaned up after sharing" + - "so they won't be present for the 2nd share attempt." - ) - return - } - val perfetto = FileProvider.getUriForFile(this, AUTHORITY, traces.first()) - val urisToShare = mutableListOf(perfetto) - traces.removeFirst() - - getZipWinscopeFileUri(traces)?.let { urisToShare.add(it) } - screenRecording?.let { urisToShare.add(it) } - - val sendIntent = - FileSender.buildSendIntent(this, urisToShare).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - - // TODO: Debug why the notification shade isn't closing upon starting the BetterBug activity - mKeyguardDismissUtil.executeWhenUnlocked( - { - startActivity(sendIntent) - false - }, - false, - false - ) - } - - private fun getZipWinscopeFileUri(traceFiles: List<File>): Uri? { - try { - externalCacheDir?.mkdirs() - val outZip: File = File.createTempFile(TEMP_FILE_PREFIX, ZIP_SUFFIX, externalCacheDir) - ZipOutputStream(FileOutputStream(outZip)).use { os -> - traceFiles.forEach { file -> - os.putNextEntry(ZipEntry(file.name)) - Files.copy(file.toPath(), os) - os.closeEntry() - } - } - return FileProvider.getUriForFile(this, AUTHORITY, outZip) - } catch (e: Exception) { - Log.e(TAG, "Failed to zip and package Recordings. Cannot share with BetterBug.", e) - return null - } - } - companion object { private const val TAG = "IssueRecordingService" private const val CHANNEL_ID = "issue_record" private const val EXTRA_SCREEN_RECORD = "extra_screenRecord" - private const val EXTRA_WINSCOPE_TRACING = "extra_winscopeTracing" - private const val ZIP_SUFFIX = ".zip" - private const val TEMP_FILE_PREFIX = "winscope_recordings" - - private val DEFAULT_TRACE_TAGS = listOf<String>() - private const val DEFAULT_BUFFER_SIZE = 16384 - private const val DEFAULT_IS_INCLUDING_WINSCOPE = true - private const val DEFAULT_IS_LONG_TRACE = false - private const val DEFAULT_IS_INCLUDING_APP_TRACE = true - private const val DEFAULT_ATTACH_TO_BUGREPORT = true - private const val DEFAULT_MAX_TRACE_SIZE = 10240 - private const val DEFAULT_MAX_TRACE_DURATION_IN_MINUTES = 30 - - private val TRACE_FILE_NAME = TraceUtils.getOutputFilename(TraceUtils.RecordingType.TRACE) - private const val AUTHORITY = "com.android.systemui.fileprovider" /** * Get an intent to stop the issue recording service. @@ -223,12 +151,12 @@ constructor( fun getStartIntent( context: Context, screenRecord: Boolean, - winscopeTracing: Boolean, + traceType: PresetTraceType, ): Intent = Intent(context, IssueRecordingService::class.java) .setAction(ACTION_START) .putExtra(EXTRA_SCREEN_RECORD, screenRecord) - .putExtra(EXTRA_WINSCOPE_TRACING, winscopeTracing) + .putExtra(INTENT_EXTRA_TRACE_TYPE, traceType) } } diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt index 68b88362427a..84a063a36740 100644 --- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt @@ -19,6 +19,7 @@ package com.android.systemui.recordissue import android.annotation.SuppressLint import android.app.AlertDialog import android.content.Context +import android.content.Intent import android.content.res.ColorStateList import android.graphics.Color import android.os.Bundle @@ -45,6 +46,8 @@ import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.traceur.MessageConstants.INTENT_EXTRA_TRACE_TYPE +import com.android.traceur.TraceUtils.PresetTraceType import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -64,9 +67,19 @@ constructor( private val userFileManager: UserFileManager, private val screenCaptureDisabledDialogDelegate: ScreenCaptureDisabledDialogDelegate, private val issueRecordingState: IssueRecordingState, + private val traceurMessageSender: TraceurMessageSender, @Assisted private val onStarted: Consumer<IssueRecordingConfig>, ) : SystemUIDialog.Delegate { + private val issueTypeOptions: Map<Int, PresetTraceType> = + hashMapOf( + Pair(R.string.performance, PresetTraceType.PERFORMANCE), + Pair(R.string.user_interface, PresetTraceType.UI), + Pair(R.string.battery, PresetTraceType.BATTERY), + Pair(R.string.thermal, PresetTraceType.THERMAL) + ) + private var selectedIssueType: PresetTraceType? = null + /** To inject dependencies and allow for easier testing */ @AssistedFactory interface Factory { @@ -92,7 +105,7 @@ constructor( onStarted.accept( IssueRecordingConfig( screenRecordSwitch.isChecked, - true /* TODO: Base this on issueType selected */ + selectedIssueType ?: PresetTraceType.UNSET ) ) dismiss() @@ -100,6 +113,7 @@ constructor( false ) } + bgExecutor.execute { traceurMessageSender.bindToTraceur(dialog.context) } } override fun createDialog(): SystemUIDialog = factory.create(this) @@ -166,20 +180,25 @@ constructor( @MainThread private fun onIssueTypeClicked(context: Context, onIssueTypeSelected: Runnable) { - val selectedCategory = issueTypeButton.text.toString() val popupMenu = PopupMenu(context, issueTypeButton) - context.resources.getStringArray(R.array.qs_record_issue_types).forEachIndexed { i, cat -> - popupMenu.menu.add(0, 0, i, cat).apply { + issueTypeOptions.keys.forEach { + popupMenu.menu.add(it).apply { setIcon(R.drawable.arrow_pointing_down) - if (selectedCategory != cat) { + if (issueTypeOptions[it] != selectedIssueType) { iconTintList = ColorStateList.valueOf(Color.TRANSPARENT) } + intent = Intent().putExtra(INTENT_EXTRA_TRACE_TYPE, issueTypeOptions[it]) } } popupMenu.apply { setOnMenuItemClickListener { issueTypeButton.text = it.title + selectedIssueType = + it.intent?.getSerializableExtra( + INTENT_EXTRA_TRACE_TYPE, + PresetTraceType::class.java + ) onIssueTypeSelected.run() true } diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt new file mode 100644 index 000000000000..189336cf69bc --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recordissue/TraceurMessageSender.kt @@ -0,0 +1,157 @@ +/* + * 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.recordissue + +import android.annotation.SuppressLint +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Bundle +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.os.Message +import android.os.Messenger +import android.util.Log +import androidx.annotation.WorkerThread +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.traceur.FileSender +import com.android.traceur.MessageConstants +import com.android.traceur.TraceUtils.PresetTraceType +import javax.inject.Inject + +private const val TAG = "TraceurMessageSender" + +@SysUISingleton +class TraceurMessageSender @Inject constructor(@Background private val backgroundLooper: Looper) { + private var binder: Messenger? = null + private var isBound: Boolean = false + + private val traceurConnection = + object : ServiceConnection { + override fun onServiceConnected(className: ComponentName, service: IBinder) { + binder = Messenger(service) + isBound = true + } + + override fun onServiceDisconnected(className: ComponentName) { + binder = null + isBound = false + } + } + + @SuppressLint("WrongConstant") + @WorkerThread + fun bindToTraceur(context: Context) { + if (isBound) { + // Binding needs to happen after the phone has been unlocked. The RecordIssueTile is + // initialized before this happens though, so binding is placed at a later time, during + // normal operations that can be repeated. This check avoids calling "bindService" 2x+ + return + } + try { + val info = + context.packageManager.getPackageInfo( + MessageConstants.TRACING_APP_PACKAGE_NAME, + PackageManager.MATCH_SYSTEM_ONLY + ) + val intent = + Intent().setClassName(info.packageName, MessageConstants.TRACING_APP_ACTIVITY) + val flags = + Context.BIND_AUTO_CREATE or + Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE or + Context.BIND_WAIVE_PRIORITY + context.bindService(intent, traceurConnection, flags) + } catch (e: Exception) { + Log.e(TAG, "failed to bind to Traceur's service", e) + } + } + + @WorkerThread + fun unbindFromTraceur(context: Context) { + if (isBound) { + context.unbindService(traceurConnection) + } + } + + @WorkerThread + fun startTracing(traceType: PresetTraceType?) { + val data = + Bundle().apply { putSerializable(MessageConstants.INTENT_EXTRA_TRACE_TYPE, traceType) } + notifyTraceur(MessageConstants.START_WHAT, data) + } + + @WorkerThread fun stopTracing() = notifyTraceur(MessageConstants.STOP_WHAT) + + @WorkerThread + fun shareTraces(context: Context, screenRecord: Uri?) { + val replyHandler = Messenger(TraceurMessageHandler(context, screenRecord, backgroundLooper)) + notifyTraceur(MessageConstants.SHARE_WHAT, replyTo = replyHandler) + } + + @WorkerThread + private fun notifyTraceur(what: Int, data: Bundle = Bundle(), replyTo: Messenger? = null) { + try { + binder!!.send( + Message.obtain().apply { + this.what = what + this.data = data + this.replyTo = replyTo + } + ) + } catch (e: Exception) { + Log.e(TAG, "failed to notify Traceur", e) + } + } + + private class TraceurMessageHandler( + private val context: Context, + private val screenRecord: Uri?, + looper: Looper, + ) : Handler(looper) { + + override fun handleMessage(msg: Message) { + if (MessageConstants.SHARE_WHAT == msg.what) { + shareTraces( + msg.data.getParcelable(MessageConstants.EXTRA_PERFETTO, Uri::class.java), + msg.data.getParcelable(MessageConstants.EXTRA_WINSCOPE, Uri::class.java) + ) + } else { + throw IllegalArgumentException("received unknown msg.what: " + msg.what) + } + } + + private fun shareTraces(perfetto: Uri?, winscope: Uri?) { + val uris: List<Uri> = + mutableListOf<Uri>().apply { + perfetto?.let { add(it) } + winscope?.let { add(it) } + screenRecord?.let { add(it) } + } + val fileSharingIntent = + FileSender.buildSendIntent(context, uris) + .addFlags( + Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT + ) + context.startActivity(fileSharingIntent) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt b/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt index 3c0aa384d98c..09fd7df15dc2 100644 --- a/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/retail/data/repository/RetailModeRepository.kt @@ -66,9 +66,9 @@ constructor( } } - globalSettings.registerContentObserver(RETAIL_MODE_SETTING, observer) + globalSettings.registerContentObserverSync(RETAIL_MODE_SETTING, observer) - awaitClose { globalSettings.unregisterContentObserver(observer) } + awaitClose { globalSettings.unregisterContentObserverSync(observer) } } .onStart { emit(Unit) } .map { globalSettings.getInt(RETAIL_MODE_SETTING, 0) != 0 } diff --git a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt index 8169dec9ede6..7a9d09ad815a 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/KeyguardlessSceneContainerFrameworkModule.kt @@ -20,6 +20,7 @@ import com.android.systemui.CoreStartable import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.scene.domain.startable.SceneContainerStartable +import com.android.systemui.scene.domain.startable.ScrimStartable import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.shared.flag.DualShade @@ -50,6 +51,11 @@ interface KeyguardlessSceneContainerFrameworkModule { @Binds @IntoMap + @ClassKey(ScrimStartable::class) + fun scrimStartable(impl: ScrimStartable): CoreStartable + + @Binds + @IntoMap @ClassKey(WindowRootViewVisibilityInteractor::class) fun bindWindowRootViewVisibilityInteractor( impl: WindowRootViewVisibilityInteractor diff --git a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt index 9bd2694f7e82..7e6dfb8762cd 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt @@ -21,6 +21,7 @@ import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlagsModule import com.android.systemui.notifications.ui.composable.NotificationsShadeSessionModule import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor import com.android.systemui.scene.domain.startable.SceneContainerStartable +import com.android.systemui.scene.domain.startable.ScrimStartable import com.android.systemui.scene.shared.model.SceneContainerConfig import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.shared.flag.DualShade @@ -56,6 +57,11 @@ interface SceneContainerFrameworkModule { @Binds @IntoMap + @ClassKey(ScrimStartable::class) + fun scrimStartable(impl: ScrimStartable): CoreStartable + + @Binds + @IntoMap @ClassKey(WindowRootViewVisibilityInteractor::class) fun bindWindowRootViewVisibilityInteractor( impl: WindowRootViewVisibilityInteractor diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt new file mode 100644 index 000000000000..c6f51b384a0a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/ScrimStartable.kt @@ -0,0 +1,236 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.scene.domain.startable + +import androidx.annotation.VisibleForTesting +import com.android.compose.animation.scene.ObservableTransitionState +import com.android.compose.animation.scene.SceneKey +import com.android.systemui.CoreStartable +import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.keyguard.domain.interactor.BiometricUnlockInteractor +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.keyguard.shared.model.BiometricUnlockMode +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.settings.brightness.domain.interactor.BrightnessMirrorShowingInteractor +import com.android.systemui.statusbar.phone.DozeServiceHost +import com.android.systemui.statusbar.phone.ScrimController +import com.android.systemui.statusbar.phone.ScrimState +import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch + +@SysUISingleton +class ScrimStartable +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + private val scrimController: ScrimController, + sceneInteractor: SceneInteractor, + deviceEntryInteractor: DeviceEntryInteractor, + keyguardInteractor: KeyguardInteractor, + occlusionInteractor: SceneContainerOcclusionInteractor, + biometricUnlockInteractor: BiometricUnlockInteractor, + private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager, + private val alternateBouncerInteractor: AlternateBouncerInteractor, + brightnessMirrorShowingInteractor: BrightnessMirrorShowingInteractor, + private val dozeServiceHost: DozeServiceHost, +) : CoreStartable { + + @VisibleForTesting + val scrimState: Flow<ScrimState?> = + combine( + deviceEntryInteractor.isDeviceEntered, + occlusionInteractor.invisibleDueToOcclusion, + sceneInteractor.currentScene, + sceneInteractor.transitionState, + keyguardInteractor.isDozing, + keyguardInteractor.isDreaming, + biometricUnlockInteractor.unlockState, + brightnessMirrorShowingInteractor.isShowing, + keyguardInteractor.isPulsing, + conflatedCallbackFlow { + val listener = + DozeServiceHost.HasPendingScreenOffCallbackChangeListener { + hasPendingScreenOffCallback -> + trySend(hasPendingScreenOffCallback) + } + dozeServiceHost.setHasPendingScreenOffCallbackChangeListener(listener) + awaitClose { + dozeServiceHost.setHasPendingScreenOffCallbackChangeListener(null) + } + }, + ) { flowValues -> + val isDeviceEntered = flowValues[0] as Boolean + val isOccluded = flowValues[1] as Boolean + val currentScene = flowValues[2] as SceneKey + val transitionState = flowValues[3] as ObservableTransitionState + val isDozing = flowValues[4] as Boolean + val isDreaming = flowValues[5] as Boolean + val biometricUnlockState = flowValues[6] as BiometricUnlockModel + val isBrightnessMirrorVisible = flowValues[7] as Boolean + val isPulsing = flowValues[8] as Boolean + val hasPendingScreenOffCallback = flowValues[9] as Boolean + + // This is true when the lockscreen scene is either the current scene or somewhere + // in the + // navigation back stack of scenes. + val isOnKeyguard = !isDeviceEntered + val isCurrentSceneBouncer = currentScene == Scenes.Bouncer + // This is true when moving away from one of the keyguard scenes to the gone scene. + // It + // happens only when unlocking or when dismissing a dismissible lockscreen. + val isTransitioningAwayFromKeyguard = + transitionState is ObservableTransitionState.Transition && + transitionState.fromScene.isKeyguard() && + transitionState.toScene == Scenes.Gone + + // This is true when any of the shade scenes is the current scene. + val isCurrentSceneShade = currentScene.isShade() + // This is true when moving into one of the shade scenes when a non-shade scene. + val isTransitioningToShade = + transitionState is ObservableTransitionState.Transition && + !transitionState.fromScene.isShade() && + transitionState.toScene.isShade() + + // This is true after completing a transition to communal. + val isIdleOnCommunal = transitionState.isIdle(Scenes.Communal) + + // This is true during the process of an unlock of the device. + // TODO(b/330587738): add support for remote unlock animations. If such an + // animation is underway, unlocking should be true. + val unlocking = + isOnKeyguard && + (biometricUnlockState.mode == BiometricUnlockMode.WAKE_AND_UNLOCK || + isTransitioningAwayFromKeyguard) + + if (alternateBouncerInteractor.isVisibleState()) { + // This will cancel the keyguardFadingAway animation if it is running. We need + // to do + // this as otherwise it can remain pending and leave keyguard in a weird state. + onKeyguardFadedAway(isTransitioningAwayFromKeyguard) + if (!isTransitioningToShade) { + // Safeguard which prevents the scrim from being stuck in the wrong + // state + Model(scrimState = ScrimState.KEYGUARD, unlocking = unlocking) + } else { + // Assume scrim state for shade is already correct and do nothing + null + } + } else if (isCurrentSceneBouncer && !unlocking) { + // Bouncer needs the front scrim when it's on top of an activity, tapping on a + // notification, editing QS or being dismissed by + // FLAG_DISMISS_KEYGUARD_ACTIVITY. + Model( + scrimState = + if (statusBarKeyguardViewManager.primaryBouncerNeedsScrimming()) { + ScrimState.BOUNCER_SCRIMMED + } else { + ScrimState.BOUNCER + }, + unlocking = false, + ) + } else if (isBrightnessMirrorVisible) { + Model(scrimState = ScrimState.BRIGHTNESS_MIRROR, unlocking = unlocking) + } else if (isCurrentSceneShade && !isDeviceEntered) { + Model(scrimState = ScrimState.SHADE_LOCKED, unlocking = unlocking) + } else if (isPulsing) { + Model(scrimState = ScrimState.PULSING, unlocking = unlocking) + } else if (hasPendingScreenOffCallback) { + Model(scrimState = ScrimState.OFF, unlocking = unlocking) + } else if (isDozing && !unlocking) { + // This will cancel the keyguardFadingAway animation if it is running. We need + // to do + // this as otherwise it can remain pending and leave keyguard in a weird state. + onKeyguardFadedAway(isTransitioningAwayFromKeyguard) + Model(scrimState = ScrimState.AOD, unlocking = false) + } else if (isIdleOnCommunal) { + if (isOnKeyguard && isDreaming && !unlocking) { + Model(scrimState = ScrimState.GLANCEABLE_HUB_OVER_DREAM, unlocking = false) + } else { + Model(scrimState = ScrimState.GLANCEABLE_HUB, unlocking = unlocking) + } + } else if (isOnKeyguard && !unlocking && !isOccluded) { + Model(scrimState = ScrimState.KEYGUARD, unlocking = false) + } else if (isOnKeyguard && !unlocking && isDreaming) { + Model(scrimState = ScrimState.DREAMING, unlocking = false) + } else { + Model(scrimState = ScrimState.UNLOCKED, unlocking = unlocking) + } + } + .onEach { model -> + if (model != null) { + scrimController.setExpansionAffectsAlpha(!model.unlocking) + } + } + .map { model -> model?.scrimState } + + override fun start() { + if (!SceneContainerFlag.isEnabled) { + return + } + + hydrateScrimState() + } + + private fun hydrateScrimState() { + applicationScope.launch { + scrimState.filterNotNull().collect { scrimState -> + scrimController.transitionTo(scrimState) + } + } + } + + private fun onKeyguardFadedAway(isKeyguardGoingAway: Boolean) { + if (isKeyguardGoingAway) { + statusBarKeyguardViewManager.onKeyguardFadedAway() + } + } + + private fun SceneKey.isKeyguard(): Boolean { + return this == Scenes.Lockscreen || this == Scenes.Bouncer + } + + private fun SceneKey.isShade(): Boolean { + return this == Scenes.Shade || + this == Scenes.QuickSettings || + this == Scenes.NotificationsShade || + this == Scenes.QuickSettingsShade + } + + private data class Model( + val scrimState: ScrimState, + val unlocking: Boolean, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java index 37f2a21b145e..498107624f9b 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessController.java @@ -137,14 +137,14 @@ public class BrightnessController implements ToggleSlider.Listener, MirroredBrig public void startObserving() { if (!mObserving) { mObserving = true; - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( BRIGHTNESS_MODE_URI, false, this, UserHandle.USER_ALL); } } public void stopObserving() { - mSecureSettings.unregisterContentObserver(this); + mSecureSettings.unregisterContentObserverSync(this); mObserving = false; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index 1d8b7e5b6155..bf0843b8fa4e 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.content.Context 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.lifecycleScope 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.res.R 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 @@ constructor( 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 @@ constructor( ) 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 @@ constructor( lifecycleRegistry.currentState = Lifecycle.State.CREATED communalContainerView = null } + + communalContainerWrapper?.let { + (it.parent as ViewGroup).removeView(it) + communalContainerWrapper = null + } } /** @@ -319,6 +360,18 @@ constructor( */ 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 @@ constructor( 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 @@ constructor( 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 @@ constructor( * 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 6df8ac4c2145..4f6a64f043d2 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.internal.policy.SystemBarUtils; 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 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum 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 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum JavaAdapter javaAdapter, CastController castController, SplitShadeStateController splitShadeStateController, + Lazy<CommunalTransitionViewModel> communalTransitionViewModelLazy, Lazy<LargeScreenHeaderHelper> largeScreenHeaderHelperLazy ) { SceneContainerFlag.assertInLegacyMode(); @@ -379,6 +382,7 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum mShadeRepository = shadeRepository; mShadeInteractor = shadeInteractor; mActiveNotificationsInteractor = activeNotificationsInteractor; + mCommunalTransitionViewModelLazy = communalTransitionViewModelLazy; mJavaAdapter = javaAdapter; mLockscreenShadeTransitionController.addCallback(new LockscreenShadeTransitionCallback()); @@ -458,6 +462,9 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum initNotificationStackScrollLayoutController(); mJavaAdapter.alwaysCollectFlow( mShadeInteractor.isExpandToQsEnabled(), this::setExpansionEnabledPolicy); + mJavaAdapter.alwaysCollectFlow( + mCommunalTransitionViewModelLazy.get().isUmoOnCommunal(), + this::setShouldUpdateSquishinessOnMedia); } private void initNotificationStackScrollLayoutController() { @@ -892,6 +899,12 @@ public class QuickSettingsControllerImpl implements QuickSettingsController, Dum } } + 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 5e4b1732bd42..a171d33ddb47 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 @@ public class ShadeCarrierGroupController { 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 @@ public class ShadeCarrierGroupController { 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 af06a356002b..b563cd9f0301 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 @@ constructor(@ShadeCarrierGroupControllerLog val buffer: LogBuffer) { ) } + 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 9885fe436e6c..e7fc18ebfc21 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 @@ import kotlinx.coroutines.launch 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 @@ constructor( // Now handled elsewhere. Do nothing. } override fun transitionToExpandedShade(delay: Long) { - scope.launch { + backgroundScope.launch { delay(delay) changeToShadeScene() } @@ -96,10 +98,12 @@ constructor( } 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/ImmersiveModeConfirmation.java b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java index 95768e55de34..2dfc920b4f20 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ImmersiveModeConfirmation.java @@ -318,10 +318,10 @@ public class ImmersiveModeConfirmation implements CoreStartable, CommandQueue.Ca }; // Register to listen for changes in Settings.Secure settings. - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.IMMERSIVE_MODE_CONFIRMATIONS, mContentObserver, UserHandle.USER_CURRENT); - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.USER_SETUP_COMPLETE, mContentObserver, UserHandle.USER_CURRENT); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java index d00916a1c1a8..c742f6413022 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 @@ public final class KeyboardShortcuts { 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 3cf61e211e42..8d3f7284e359 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 @@ public class NotificationGroupingUtil { } 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 854ef928847a..bb26f92e510d 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 @@ public class NotificationLockscreenUserManagerImpl implements !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 @@ public class NotificationLockscreenUserManagerImpl implements 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 455c96441927..2e87a5bedda5 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 @@ constructor( 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 @@ constructor( */ 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 c643238b7e30..682a9fff2994 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.inflation.BindEven 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 java.util.concurrent.ConcurrentHashMap 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 @@ class ConversationNotificationProcessor @Inject constructor( * 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 @@ class AnimatedImageNotificationManager @Inject constructor( /** 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 @@ class AnimatedImageNotificationManager @Inject constructor( } 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 @@ class ConversationNotificationManager @Inject constructor( } 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 @@ class ConversationNotificationManager @Inject constructor( } 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 @@ class ConversationNotificationManager @Inject constructor( // 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 @@ class ConversationNotificationManager @Inject constructor( } 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 57b41f36e51f..cafe6ffc87a3 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 98109f940289..fc47dc1ed81a 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.InflationException; 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 @@ public class NotifInflaterImpl implements NotifInflater { 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 42bf4e7d0787..071192b02ee0 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 @@ constructor( // 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/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt index 4c2ef8322731..4c82bc193c77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt @@ -37,11 +37,13 @@ import com.android.systemui.util.settings.SecureSettings import javax.inject.Inject /** - * A class which provides an adjustment object to the preparation coordinator which is uses - * to ensure that notifications are reinflated when ranking-derived information changes. + * A class which provides an adjustment object to the preparation coordinator which is uses to + * ensure that notifications are reinflated when ranking-derived information changes. */ @SysUISingleton -class NotifUiAdjustmentProvider @Inject constructor( +class NotifUiAdjustmentProvider +@Inject +constructor( @Main private val handler: Handler, private val secureSettings: SecureSettings, private val lockscreenUserManager: NotificationLockscreenUserManager, @@ -53,14 +55,13 @@ class NotifUiAdjustmentProvider @Inject constructor( private val dirtyListeners = ListenerSet<Runnable>() private var isSnoozeSettingsEnabled = false - /** - * Update the snooze enabled value on user switch - */ - private val userTrackerCallback = object : UserTracker.Callback { - override fun onUserChanged(newUser: Int, userContext: Context) { - updateSnoozeEnabled() + /** Update the snooze enabled value on user switch */ + private val userTrackerCallback = + object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + updateSnoozeEnabled() + } } - } init { userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler)) @@ -75,7 +76,7 @@ class NotifUiAdjustmentProvider @Inject constructor( ) } updateSnoozeEnabled() - secureSettings.registerContentObserverForUser( + secureSettings.registerContentObserverForUserSync( SHOW_NOTIFICATION_SNOOZE, settingsObserver, UserHandle.USER_ALL @@ -93,7 +94,7 @@ class NotifUiAdjustmentProvider @Inject constructor( onSensitiveStateChangedListener ) } - secureSettings.unregisterContentObserver(settingsObserver) + secureSettings.unregisterContentObserverSync(settingsObserver) } } @@ -104,12 +105,13 @@ class NotifUiAdjustmentProvider @Inject constructor( private val onSensitiveStateChangedListener = Runnable { dirtyListeners.forEach(Runnable::run) } - private val settingsObserver = object : ContentObserver(handler) { - override fun onChange(selfChange: Boolean) { - updateSnoozeEnabled() - dirtyListeners.forEach(Runnable::run) + private val settingsObserver = + object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean) { + updateSnoozeEnabled() + dirtyListeners.forEach(Runnable::run) + } } - } private fun updateSnoozeEnabled() { isSnoozeSettingsEnabled = @@ -126,22 +128,23 @@ class NotifUiAdjustmentProvider @Inject constructor( } /** - * Returns a adjustment object for the given entry. This can be compared to a previous instance + * Returns a adjustment object for the given entry. This can be compared to a previous instance * from the same notification using [NotifUiAdjustment.needReinflate] to determine if it should * be reinflated. */ - fun calculateAdjustment(entry: NotificationEntry) = NotifUiAdjustment( - key = entry.key, - smartActions = entry.ranking.smartActions, - smartReplies = entry.ranking.smartReplies, - isConversation = entry.ranking.isConversation, - isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled, - isMinimized = isEntryMinimized(entry), - needsRedaction = - lockscreenUserManager.needsRedaction(entry) || - (screenshareNotificationHiding() && - sensitiveNotifProtectionController.shouldProtectNotification(entry)), - isChildInGroup = entry.hasEverBeenGroupChild(), - isGroupSummary = entry.hasEverBeenGroupSummary(), - ) + fun calculateAdjustment(entry: NotificationEntry) = + NotifUiAdjustment( + key = entry.key, + smartActions = entry.ranking.smartActions, + smartReplies = entry.ranking.smartReplies, + isConversation = entry.ranking.isConversation, + isSnoozeEnabled = isSnoozeSettingsEnabled && !entry.isCanceled, + isMinimized = isEntryMinimized(entry), + needsRedaction = + lockscreenUserManager.needsRedaction(entry) || + (screenshareNotificationHiding() && + sensitiveNotifProtectionController.shouldProtectNotification(entry)), + isChildInGroup = entry.hasEverBeenGroupChild(), + isGroupSummary = entry.hasEverBeenGroupSummary(), + ) } 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 319b49972bd2..16d0cc42db7f 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 @@ package com.android.systemui.statusbar.notification.icon 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 271b0a86ca12..3df9374da914 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 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 @@ constructor( 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 @@ constructor( @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) + 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 = descriptor + } else if (usingMonochromeAppIcon) { + // When notificationsUseMonochromeAppIcon is enabled, we use the appIconDescriptor. + entry.icons.appIconDescriptor = descriptor } 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()) { + // 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 = sbi + entry.icons.peopleAvatarDescriptor = descriptor } else { - entry.icons.smallIconDescriptor = sbi + entry.icons.smallIconDescriptor = descriptor } } - - return sbi } @Throws(InflationException::class) @@ -276,6 +324,29 @@ constructor( ) } + // 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 442c0978fd77..d029ce722af9 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 @@ public final class IconPack { @Nullable private final StatusBarIconView mAodIcon; @Nullable private StatusBarIcon mSmallIconDescriptor; + @Nullable private StatusBarIcon mAppIconDescriptor; @Nullable private StatusBarIcon mPeopleAvatarDescriptor; private boolean mIsImportantConversation; @@ -111,6 +112,15 @@ public final class IconPack { 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 c74c396741d7..c29d700396af 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.Context 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 @@ class AvalancheProvider constructor( private val broadcastDispatcher: BroadcastDispatcher, private val logger: VisualInterruptionDecisionLogger, + private val uiEventLogger: UiEventLogger, ) { val TAG = "AvalancheProvider" val timeoutMs = 120000 @@ -56,6 +57,7 @@ constructor( 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 938a71fd7b82..f84b5f48c864 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.os.PowerManager 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.UiEvent +import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker @@ -84,7 +86,7 @@ class PeekDisabledSuppressor( } } - globalSettings.registerContentObserver( + globalSettings.registerContentObserverSync( globalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED), /* notifyForDescendants = */ true, observer @@ -92,7 +94,7 @@ class PeekDisabledSuppressor( // QQQ: Do we need to register for SETTING_HEADS_UP_TICKER? It seems unused. - observer.onChange(/* selfChange = */ true) + observer.onChange(/* selfChange= */ true) } } @@ -247,6 +249,7 @@ class AvalancheSuppressor( private val systemClock: SystemClock, private val systemSettings: SystemSettings, private val packageManager: PackageManager, + private val uiEventLogger: UiEventLogger, ) : VisualInterruptionFilter( types = setOf(PEEK, PULSE), @@ -266,6 +269,39 @@ class AvalancheSuppressor( 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 +323,46 @@ class AvalancheSuppressor( 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/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index 332ece428a6e..c7548aae7131 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -14,7 +14,6 @@ import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.NotificationLockscreenUserManager @@ -41,8 +40,8 @@ import javax.inject.Inject /** Determines if notifications should be visible based on the state of the keyguard. */ interface KeyguardNotificationVisibilityProvider { /** - * Determines if the given notification should be hidden based on the current keyguard state. - * If a [Consumer] registered via [addOnStateChangedListener] is invoked, the results of this + * Determines if the given notification should be hidden based on the current keyguard state. If + * a [Consumer] registered via [addOnStateChangedListener] is invoked, the results of this * method may no longer be valid and should be re-queried. */ fun shouldHideNotification(entry: NotificationEntry): Boolean @@ -61,8 +60,9 @@ object KeyguardNotificationVisibilityProviderModule @Module interface KeyguardNotificationVisibilityProviderImplModule { @Binds - fun bindImpl(impl: KeyguardNotificationVisibilityProviderImpl): - KeyguardNotificationVisibilityProvider + fun bindImpl( + impl: KeyguardNotificationVisibilityProviderImpl + ): KeyguardNotificationVisibilityProvider @Binds @IntoMap @@ -71,7 +71,9 @@ interface KeyguardNotificationVisibilityProviderImplModule { } @SysUISingleton -class KeyguardNotificationVisibilityProviderImpl @Inject constructor( +class KeyguardNotificationVisibilityProviderImpl +@Inject +constructor( @Main private val handler: Handler, private val keyguardStateController: KeyguardStateController, private val lockscreenUserManager: NotificationLockscreenUserManager, @@ -84,76 +86,88 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( private val featureFlags: FeatureFlagsClassic ) : CoreStartable, KeyguardNotificationVisibilityProvider { private val showSilentNotifsUri = - secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS) + secureSettings.getUriFor(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS) private val onStateChangedListeners = ListenerSet<Consumer<String>>() private var hideSilentNotificationsOnLockscreen: Boolean = false - private val userTrackerCallback = object : UserTracker.Callback { - override fun onUserChanged(newUser: Int, userContext: Context) { - readShowSilentNotificationSetting() - if (isLockedOrLocking) { - // maybe public mode changed - notifyStateChanged("onUserSwitched") + private val userTrackerCallback = + object : UserTracker.Callback { + override fun onUserChanged(newUser: Int, userContext: Context) { + readShowSilentNotificationSetting() + if (isLockedOrLocking) { + // maybe public mode changed + notifyStateChanged("onUserSwitched") + } } } - } override fun start() { readShowSilentNotificationSetting() - keyguardStateController.addCallback(object : KeyguardStateController.Callback { - override fun onUnlockedChanged() { - notifyStateChanged("onUnlockedChanged") - } + keyguardStateController.addCallback( + object : KeyguardStateController.Callback { + override fun onUnlockedChanged() { + notifyStateChanged("onUnlockedChanged") + } - override fun onKeyguardShowingChanged() { - notifyStateChanged("onKeyguardShowingChanged") + override fun onKeyguardShowingChanged() { + notifyStateChanged("onKeyguardShowingChanged") + } } - }) - keyguardUpdateMonitor.registerCallback(object : KeyguardUpdateMonitorCallback() { - override fun onStrongAuthStateChanged(userId: Int) { - notifyStateChanged("onStrongAuthStateChanged") + ) + keyguardUpdateMonitor.registerCallback( + object : KeyguardUpdateMonitorCallback() { + override fun onStrongAuthStateChanged(userId: Int) { + notifyStateChanged("onStrongAuthStateChanged") + } } - }) + ) // register lockscreen settings changed callbacks: - val settingsObserver: ContentObserver = object : ContentObserver(handler) { - override fun onChange(selfChange: Boolean, uri: Uri?) { - if (uri == showSilentNotifsUri) { - readShowSilentNotificationSetting() - } - if (isLockedOrLocking) { - notifyStateChanged("Settings $uri changed") + val settingsObserver: ContentObserver = + object : ContentObserver(handler) { + override fun onChange(selfChange: Boolean, uri: Uri?) { + if (uri == showSilentNotifsUri) { + readShowSilentNotificationSetting() + } + if (isLockedOrLocking) { + notifyStateChanged("Settings $uri changed") + } } } - } - secureSettings.registerContentObserverForUser( - Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, - settingsObserver, - UserHandle.USER_ALL) + secureSettings.registerContentObserverForUserSync( + Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, + settingsObserver, + UserHandle.USER_ALL + ) - secureSettings.registerContentObserverForUser( - Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, - true, - settingsObserver, - UserHandle.USER_ALL) + secureSettings.registerContentObserverForUserSync( + Settings.Secure.LOCK_SCREEN_ALLOW_PRIVATE_NOTIFICATIONS, + true, + settingsObserver, + UserHandle.USER_ALL + ) - globalSettings.registerContentObserver(Settings.Global.ZEN_MODE, settingsObserver) + globalSettings.registerContentObserverSync(Settings.Global.ZEN_MODE, settingsObserver) - secureSettings.registerContentObserverForUser( - Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, - settingsObserver, - UserHandle.USER_ALL) + secureSettings.registerContentObserverForUserSync( + Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, + settingsObserver, + UserHandle.USER_ALL + ) // register (maybe) public mode changed callbacks: - statusBarStateController.addCallback(object : StatusBarStateController.StateListener { - override fun onStateChanged(newState: Int) { - notifyStateChanged("onStatusBarStateChanged") - } - override fun onUpcomingStateChanged(state: Int) { - notifyStateChanged("onStatusBarUpcomingStateChanged") + statusBarStateController.addCallback( + object : StatusBarStateController.StateListener { + override fun onStateChanged(newState: Int) { + notifyStateChanged("onStatusBarStateChanged") + } + + override fun onUpcomingStateChanged(state: Int) { + notifyStateChanged("onStatusBarUpcomingStateChanged") + } } - }) + ) userTracker.addCallback(userTrackerCallback, HandlerExecutor(handler)) } @@ -169,45 +183,48 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( onStateChangedListeners.forEach { it.accept(reason) } } - override fun shouldHideNotification(entry: NotificationEntry): Boolean = when { - // Keyguard state doesn't matter if the keyguard is not showing. - !isLockedOrLocking -> false - // Notifications not allowed on the lockscreen, always hide. - !lockscreenUserManager.shouldShowLockscreenNotifications() -> true - // User settings do not allow this notification on the lockscreen, so hide it. - userSettingsDisallowNotification(entry) -> true - // Entry is explicitly marked SECRET, so hide it. - entry.sbn.notification.visibility == VISIBILITY_SECRET -> true - // if entry is silent, apply custom logic to see if should hide - shouldHideIfEntrySilent(entry) -> true - else -> false - } + override fun shouldHideNotification(entry: NotificationEntry): Boolean = + when { + // Keyguard state doesn't matter if the keyguard is not showing. + !isLockedOrLocking -> false + // Notifications not allowed on the lockscreen, always hide. + !lockscreenUserManager.shouldShowLockscreenNotifications() -> true + // User settings do not allow this notification on the lockscreen, so hide it. + userSettingsDisallowNotification(entry) -> true + // Entry is explicitly marked SECRET, so hide it. + entry.sbn.notification.visibility == VISIBILITY_SECRET -> true + // if entry is silent, apply custom logic to see if should hide + shouldHideIfEntrySilent(entry) -> true + else -> false + } - private fun shouldHideIfEntrySilent(entry: ListEntry): Boolean = when { - // Show if explicitly high priority (not hidden) - highPriorityProvider.isExplicitlyHighPriority(entry) -> false - // Ambient notifications are hidden always from lock screen - entry.representativeEntry?.isAmbient == true -> true - // [Now notification is silent] - // Hide regardless of parent priority if user wants silent notifs hidden - hideSilentNotificationsOnLockscreen -> true - // Parent priority is high enough to be shown on the lockscreen, do not hide. - entry.parent?.let(::shouldHideIfEntrySilent) == false -> false - // Show when silent notifications are allowed on lockscreen - else -> false - } + private fun shouldHideIfEntrySilent(entry: ListEntry): Boolean = + when { + // Show if explicitly high priority (not hidden) + highPriorityProvider.isExplicitlyHighPriority(entry) -> false + // Ambient notifications are hidden always from lock screen + entry.representativeEntry?.isAmbient == true -> true + // [Now notification is silent] + // Hide regardless of parent priority if user wants silent notifs hidden + hideSilentNotificationsOnLockscreen -> true + // Parent priority is high enough to be shown on the lockscreen, do not hide. + entry.parent?.let(::shouldHideIfEntrySilent) == false -> false + // Show when silent notifications are allowed on lockscreen + else -> false + } private fun userSettingsDisallowNotification(entry: NotificationEntry): Boolean { - fun disallowForUser(user: Int) = when { - // user is in lockdown, always disallow - keyguardUpdateMonitor.isUserInLockdown(user) -> true - // device isn't public, no need to check public-related settings, so allow - !lockscreenUserManager.isLockscreenPublicMode(user) -> false - // entry is meant to be secret on the lockscreen, disallow - isRankingVisibilitySecret(entry) -> true - // disallow if user disallows notifications in public - else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user) - } + fun disallowForUser(user: Int) = + when { + // user is in lockdown, always disallow + keyguardUpdateMonitor.isUserInLockdown(user) -> true + // device isn't public, no need to check public-related settings, so allow + !lockscreenUserManager.isLockscreenPublicMode(user) -> false + // entry is meant to be secret on the lockscreen, disallow + isRankingVisibilitySecret(entry) -> true + // disallow if user disallows notifications in public + else -> !lockscreenUserManager.userAllowsNotificationsInPublic(user) + } val currentUser = lockscreenUserManager.currentUserId val notifUser = entry.sbn.user.identifier return when { @@ -222,28 +239,35 @@ class KeyguardNotificationVisibilityProviderImpl @Inject constructor( // ranking.lockscreenVisibilityOverride contains possibly out of date DPC and Setting // info, and NotificationLockscreenUserManagerImpl is already listening for updates // to those - return entry.ranking.channel != null && entry.ranking.channel.lockscreenVisibility == - VISIBILITY_SECRET + return entry.ranking.channel != null && + entry.ranking.channel.lockscreenVisibility == VISIBILITY_SECRET } - override fun dump(pw: PrintWriter, args: Array<out String>) = pw.asIndenting().run { - println("isLockedOrLocking", isLockedOrLocking) - withIncreasedIndent { - println("keyguardStateController.isShowing", keyguardStateController.isShowing) - println("statusBarStateController.currentOrUpcomingState", - statusBarStateController.currentOrUpcomingState) + override fun dump(pw: PrintWriter, args: Array<out String>) = + pw.asIndenting().run { + println("isLockedOrLocking", isLockedOrLocking) + withIncreasedIndent { + println("keyguardStateController.isShowing", keyguardStateController.isShowing) + println( + "statusBarStateController.currentOrUpcomingState", + statusBarStateController.currentOrUpcomingState + ) + } + println("hideSilentNotificationsOnLockscreen", hideSilentNotificationsOnLockscreen) } - println("hideSilentNotificationsOnLockscreen", hideSilentNotificationsOnLockscreen) - } - private val isLockedOrLocking get() = - keyguardStateController.isShowing || + private val isLockedOrLocking + get() = + keyguardStateController.isShowing || statusBarStateController.currentOrUpcomingState == StatusBarState.KEYGUARD private fun readShowSilentNotificationSetting() { val showSilentNotifs = - secureSettings.getBoolForUser(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, - false, UserHandle.USER_CURRENT) + secureSettings.getBoolForUser( + Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, + false, + UserHandle.USER_CURRENT + ) hideSilentNotificationsOnLockscreen = !showSilentNotifs } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java index 74925c8dd506..fea360d4e02a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java @@ -171,11 +171,11 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter }; if (ENABLE_HEADS_UP) { - mGlobalSettings.registerContentObserver( + mGlobalSettings.registerContentObserverSync( mGlobalSettings.getUriFor(HEADS_UP_NOTIFICATIONS_ENABLED), true, headsUpObserver); - mGlobalSettings.registerContentObserver( + mGlobalSettings.registerContentObserverSync( mGlobalSettings.getUriFor(SETTING_HEADS_UP_TICKER), true, headsUpObserver); } 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 7e16cd5a693f..84f8662f5fee 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 @@ constructor( 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 0fdf681aac26..1efa56f9bb09 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 @@ package com.android.systemui.statusbar.notification.logging 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 @@ import javax.inject.Inject class NotificationMemoryMonitor @Inject constructor( - private val featureFlags: FeatureFlags, private val notificationMemoryDumper: NotificationMemoryDumper, private val notificationMemoryLogger: Lazy<NotificationMemoryLogger>, ) : CoreStartable { @@ -42,9 +39,6 @@ constructor( 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 99ce454126cf..190a2cd269ae 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 @@ public class ExpandableNotificationRow extends ActivatableNotificationView * 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 2f038714212b..6ba26d99419e 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder private final NotifLayoutInflaterFactory.Provider mNotifLayoutInflaterFactoryProvider; private final HeadsUpStyleProvider mHeadsUpStyleProvider; - private final NotificationContentInflaterLogger mLogger; + private final NotificationRowContentBinderLogger mLogger; @Inject NotificationContentInflater( @@ -104,7 +104,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder SmartReplyStateInflater smartRepliesInflater, NotifLayoutInflaterFactory.Provider notifLayoutInflaterFactoryProvider, HeadsUpStyleProvider headsUpStyleProvider, - NotificationContentInflaterLogger logger) { + NotificationRowContentBinderLogger logger) { mRemoteViewCache = remoteViewCache; mRemoteInputManager = remoteInputManager; mConversationProcessor = conversationProcessor; @@ -345,7 +345,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder NotificationViewWrapper existingWrapper, final HashMap<Integer, CancellationSignal> runningInflations, ApplyCallback applyCallback, - NotificationContentInflaterLogger logger) { + NotificationRowContentBinderLogger logger) { RemoteViews newContentView = applyCallback.getRemoteView(); if (inflateSynchronously) { try { @@ -845,7 +845,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder @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 @@ public class NotificationContentInflater implements NotificationRowContentBinder 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 @@ public class NotificationContentInflater implements NotificationRowContentBinder 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 33339a7fe025..c1302a0d3e57 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.IntDef; 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 @@ public interface NotificationRowContentBinder { @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 index 15c705579bf7..a32e1d738fcc 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.NotificationRowContentBin 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 @@ constructor(@NotifInflationLog private val buffer: LogBuffer) { } } -private const val TAG = "NotificationContentInflater" +private const val TAG = "NotificationRowContentBinder" diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java index a17c066953e1..30dbfed0349f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSettingsController.java @@ -92,9 +92,9 @@ public class NotificationSettingsController implements Dumpable { synchronized (mListeners) { if (mListeners.size() > 0) { - mSecureSettings.unregisterContentObserver(mContentObserver); + mSecureSettings.unregisterContentObserverSync(mContentObserver); for (Uri uri : mListeners.keySet()) { - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( uri, false, mContentObserver, newUser); } } @@ -131,7 +131,7 @@ public class NotificationSettingsController implements Dumpable { mListeners.put(uri, currentListeners); if (currentListeners.size() == 1) { mBackgroundHandler.post(() -> { - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( uri, false, mContentObserver, mUserTracker.getUserId()); }); } @@ -159,7 +159,7 @@ public class NotificationSettingsController implements Dumpable { if (mListeners.size() == 0) { mBackgroundHandler.post(() -> { - mSecureSettings.unregisterContentObserver(mContentObserver); + mSecureSettings.unregisterContentObserverSync(mContentObserver); }); } } 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 bae89fbf626f..427fb66ca2d0 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 @@ public final class RowContentBindParams { /** * 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 3fce9ce5837d..6fc82c9bcd7e 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 @@ internal object SingleLineViewInflater { 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 @@ internal object SingleLineViewInflater { 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/media/controls/util/MediaControlsRefactorFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationRowContentBinderRefactor.kt index 2850b4bb2358..997acdd685e1 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/util/MediaControlsRefactorFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/shared/NotificationRowContentBinderRefactor.kt @@ -14,26 +14,26 @@ * limitations under the License. */ -package com.android.systemui.media.controls.util +package com.android.systemui.statusbar.notification.row.shared 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. */ +/** Helper for reading or using the notification row content binder refactor flag state. */ @Suppress("NOTHING_TO_INLINE") -object MediaControlsRefactorFlag { +object NotificationRowContentBinderRefactor { /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_MEDIA_CONTROLS_REFACTOR + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_ROW_CONTENT_BINDER_REFACTOR /** A token used for dependency declaration */ val token: FlagToken get() = FlagToken(FLAG_NAME, isEnabled) - /** Is the flag enabled? */ + /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.mediaControlsRefactor() + get() = Flags.notificationRowContentBinderRefactor() /** * 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/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java index bd7f766c6860..d1fabb168d90 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 @@ public class NotificationHeaderViewWrapper extends NotificationViewWrapper imple 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 d6c73a9dda9e..2dccea668916 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 @@ object NotificationHeadsUpCycling { * 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 dd81d42b58ee..71f0de08ece3 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 @@ import com.android.systemui.flags.RefactorFlagUtils @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 @@ object NotificationThrottleHun { /** 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 db544ce59aa1..f6722a4ccff0 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 @@ class NotificationPlaceholderRepository @Inject constructor() { */ 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 afcf3ae7d5b2..8557afc6ebd3 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 @@ constructor( */ 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 @@ constructor( 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 a99fbfcc7907..e90a64a32fba 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 @@ constructor( */ 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 ea33be0ea4ed..634bd7e4cd41 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 @@ constructor( 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/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index 78a803618c8b..7567f36302b4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -2800,6 +2800,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { @Override @VisibleForTesting public void updateScrimController() { + if (SceneContainerFlag.isEnabled()) { + return; + } + Trace.beginSection("CentralSurfaces#updateScrimController"); boolean unlocking = mKeyguardStateController.isShowing() && ( @@ -2822,15 +2826,15 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // Assume scrim state for shade is already correct and do nothing } else { // Safeguard which prevents the scrim from being stuck in the wrong state - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); } } else { if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED || mTransitionToFullShadeProgress > 0f)) { - mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE); + mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE); } else { - mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED); + mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED); } } // This will cancel the keyguardFadingAway animation if it is running. We need to do @@ -2842,40 +2846,40 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // FLAG_DISMISS_KEYGUARD_ACTIVITY. ScrimState state = mStatusBarKeyguardViewManager.primaryBouncerNeedsScrimming() ? ScrimState.BOUNCER_SCRIMMED : ScrimState.BOUNCER; - mScrimController.transitionTo(state); + mScrimController.legacyTransitionTo(state); } else if (mBrightnessMirrorVisible) { - mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR); + mScrimController.legacyTransitionTo(ScrimState.BRIGHTNESS_MIRROR); } else if (mState == StatusBarState.SHADE_LOCKED) { - mScrimController.transitionTo(ScrimState.SHADE_LOCKED); + mScrimController.legacyTransitionTo(ScrimState.SHADE_LOCKED); } else if (mDozeServiceHost.isPulsing()) { - mScrimController.transitionTo(ScrimState.PULSING, + mScrimController.legacyTransitionTo(ScrimState.PULSING, mDozeScrimController.getScrimCallback()); } else if (mDozeServiceHost.hasPendingScreenOffCallback()) { - mScrimController.transitionTo(ScrimState.OFF, new ScrimController.Callback() { + mScrimController.legacyTransitionTo(ScrimState.OFF, new ScrimController.Callback() { @Override public void onFinished() { mDozeServiceHost.executePendingScreenOffCallback(); } }); } else if (mDozing && !unlocking) { - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); // This will cancel the keyguardFadingAway animation if it is running. We need to do // this as otherwise it can remain pending and leave keyguard in a weird state. mUnlockScrimCallback.onCancelled(); } else if (mIsIdleOnCommunal) { if (dreaming) { - mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); + mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); } else { - mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB); + mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB); } } else if (mKeyguardStateController.isShowing() && !mKeyguardStateController.isOccluded() && !unlocking) { - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); } else if (dreaming) { - mScrimController.transitionTo(ScrimState.DREAMING); + mScrimController.legacyTransitionTo(ScrimState.DREAMING); } else { - mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback); } updateLightRevealScrimVisibility(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java index 77f37063809c..330383ff03af 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java @@ -29,6 +29,8 @@ import android.util.Log; import android.view.MotionEvent; import android.view.View; +import androidx.annotation.Nullable; + import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.systemui.assist.AssistManager; @@ -74,6 +76,11 @@ public final class DozeServiceHost implements DozeHost { private final PowerManager mPowerManager; private boolean mAnimateWakeup; private boolean mIgnoreTouchWhilePulsing; + private final HasPendingScreenOffCallbackChangeListener + mDefaultHasPendingScreenOffCallbackChangeListener = + hasPendingScreenOffCallback -> { /* no op */ }; + private HasPendingScreenOffCallbackChangeListener mHasPendingScreenOffCallbackChangeListener = + mDefaultHasPendingScreenOffCallbackChangeListener; private Runnable mPendingScreenOffCallback; @VisibleForTesting boolean mWakeLockScreenPerformsAuth = SystemProperties.getBoolean( @@ -434,12 +441,14 @@ public final class DozeServiceHost implements DozeHost { Log.w(TAG, "Overlapping onDisplayOffCallback. Ignoring previous one."); } mPendingScreenOffCallback = onDisplayOffCallback; + mHasPendingScreenOffCallbackChangeListener.onHasPendingScreenOffCallbackChanged(true); mCentralSurfaces.updateScrimController(); } @Override public void cancelGentleSleep() { mPendingScreenOffCallback = null; + mHasPendingScreenOffCallbackChangeListener.onHasPendingScreenOffCallbackChanged(false); if (mScrimController.getState() == ScrimState.OFF) { mCentralSurfaces.updateScrimController(); } @@ -448,11 +457,27 @@ public final class DozeServiceHost implements DozeHost { /** * When the dozing host is waiting for scrims to fade out to change the display state. */ - boolean hasPendingScreenOffCallback() { + public boolean hasPendingScreenOffCallback() { return mPendingScreenOffCallback != null; } /** + * Sets a listener to be notified whenever the result of {@link #hasPendingScreenOffCallback()} + * changes. + * + * <p>Setting the listener automatically notifies the listener inline. + */ + public void setHasPendingScreenOffCallbackChangeListener( + @Nullable HasPendingScreenOffCallbackChangeListener listener) { + mHasPendingScreenOffCallbackChangeListener = listener != null + ? listener + : mDefaultHasPendingScreenOffCallbackChangeListener; + + mHasPendingScreenOffCallbackChangeListener.onHasPendingScreenOffCallbackChanged( + mPendingScreenOffCallback != null); + } + + /** * Executes an nullifies the pending display state callback. * * @see #hasPendingScreenOffCallback() @@ -464,6 +489,7 @@ public final class DozeServiceHost implements DozeHost { } mPendingScreenOffCallback.run(); mPendingScreenOffCallback = null; + mHasPendingScreenOffCallbackChangeListener.onHasPendingScreenOffCallbackChanged(false); } boolean shouldAnimateWakeup() { @@ -524,4 +550,14 @@ public final class DozeServiceHost implements DozeHost { } } }; + + /** + * Defines interface for classes that can be notified about changes to having or not having a + * pending screen-off callback. + */ + public interface HasPendingScreenOffCallbackChangeListener { + + /** Notifies that there now is or isn't a pending screen-off callback. */ + void onHasPendingScreenOffCallbackChanged(boolean hasPendingScreenOffCallback); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java index 6d4301f8475f..e44edcbd4ebb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java @@ -398,7 +398,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mSystemIconsContainer.setOnHoverListener(hoverListener); mView.setOnApplyWindowInsetsListener( (view, windowInsets) -> mView.updateWindowInsets(windowInsets, mInsetsProvider)); - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, false, mVolumeSettingObserver, @@ -416,7 +416,7 @@ public class KeyguardStatusBarViewController extends ViewController<KeyguardStat mStatusBarStateController.removeCallback(mStatusBarStateListener); mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); mDisableStateTracker.stopTracking(mCommandQueue); - mSecureSettings.unregisterContentObserver(mVolumeSettingObserver); + mSecureSettings.unregisterContentObserverSync(mVolumeSettingObserver); if (mTintedIconManager != null) { mStatusBarIconController.removeIconGroup(mTintedIconManager); } 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 b448d85bfd49..fc29eaba4b46 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 @@ constructor( 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/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index fe001b35e958..9cece762c7e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -72,6 +72,7 @@ import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerToGoneTransitionViewModel; import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.scene.shared.model.Scenes; import com.android.systemui.scrim.ScrimView; import com.android.systemui.shade.ShadeViewController; @@ -319,8 +320,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump mScrimBehind.setViewAlpha(mBehindAlpha); }; + @VisibleForTesting Consumer<TransitionStep> mBouncerToGoneTransition; + private boolean mViewsAttached; + @Inject public ScrimController( LightBarController lightBarController, @@ -432,6 +436,16 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump state.prepare(state); } + hydrateStateInternally(behindScrim); + + mViewsAttached = true; + } + + private void hydrateStateInternally(ScrimView behindScrim) { + if (SceneContainerFlag.isEnabled()) { + return; + } + // Directly control transition to UNLOCKED scrim state from PRIMARY_BOUNCER, and make sure // to report back that keyguard has faded away. This fixes cases where the scrim state was // rapidly switching on unlock, due to shifts in state in CentralSurfacesImpl @@ -443,7 +457,7 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (state == TransitionState.STARTED) { setExpansionAffectsAlpha(false); - transitionTo(ScrimState.UNLOCKED); + legacyTransitionTo(ScrimState.UNLOCKED); } if (state == TransitionState.FINISHED || state == TransitionState.CANCELED) { @@ -508,10 +522,36 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump } public void transitionTo(ScrimState state) { - transitionTo(state, null); + if (SceneContainerFlag.isUnexpectedlyInLegacyMode() || !mViewsAttached) { + return; + } + + internalTransitionTo(state, null); + } + + /** + * Transitions to the given {@link ScrimState}. + * + * @deprecated Legacy codepath only. Do not call directly. + */ + @Deprecated + public void legacyTransitionTo(ScrimState state) { + SceneContainerFlag.assertInLegacyMode(); + internalTransitionTo(state, null); + } + + /** + * Transitions to the given {@link ScrimState}. + * + * @deprecated Legacy codepath only. Do not call directly. + */ + @Deprecated + public void legacyTransitionTo(ScrimState state, Callback callback) { + SceneContainerFlag.assertInLegacyMode(); + internalTransitionTo(state, callback); } - public void transitionTo(ScrimState state, Callback callback) { + private void internalTransitionTo(ScrimState state, Callback callback) { if (mIsBouncerToGoneTransitionRunning) { Log.i(TAG, "Skipping transition to: " + state + " while mIsBouncerToGoneTransitionRunning"); 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 ebb62ec7bcac..db4f0af5ba9f 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.ExperimentalCoroutinesApi; 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 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } 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/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 479aef167b5b..c53558ea7fd2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -23,9 +23,9 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.WakefulnessLifecycle -import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor import com.android.systemui.shade.ShadeViewController import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor +import com.android.systemui.shade.domain.interactor.ShadeLockscreenInteractor import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationShadeWindowController @@ -162,7 +162,7 @@ constructor( this.centralSurfaces = centralSurfaces updateAnimatorDurationScale() - globalSettings.registerContentObserver( + globalSettings.registerContentObserverSync( Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE), /* notify for descendants */ false, animatorDurationScaleObserver @@ -376,8 +376,9 @@ constructor( // We currently draw both the light reveal scrim, and the AOD UI, in the shade. If it's // already expanded and showing notifications/QS, the animation looks really messy. For now, // disable it if the notification panel is expanded. - if ((!this::centralSurfaces.isInitialized || - panelExpansionInteractorLazy.get().isPanelExpanded) && + if ( + (!this::centralSurfaces.isInitialized || + panelExpansionInteractorLazy.get().isPanelExpanded) && // Status bar might be expanded because we have started // playing the animation already !isAnimationPlaying() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index aac211ae6b46..3d8090d70faa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -80,6 +80,10 @@ import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListen import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener; import com.android.systemui.util.settings.SecureSettings; +import kotlin.Unit; + +import kotlinx.coroutines.DisposableHandle; + import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -90,10 +94,6 @@ import java.util.concurrent.Executor; import javax.inject.Inject; -import kotlin.Unit; - -import kotlinx.coroutines.DisposableHandle; - /** * Contains the collapsed status bar and handles hiding/showing based on disable flags * and keyguard state. Also manages lifecycle to make sure the views it contains are being @@ -431,7 +431,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue initOngoingCallChip(); mAnimationScheduler.addCallback(this); - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.STATUS_BAR_SHOW_VIBRATE_ICON, false, mVolumeSettingObserver, @@ -445,7 +445,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mStatusBarStateController.removeCallback(this); mOngoingCallController.removeCallback(mOngoingCallListener); mAnimationScheduler.removeCallback(this); - mSecureSettings.unregisterContentObserver(mVolumeSettingObserver); + mSecureSettings.unregisterContentObserverSync(mVolumeSettingObserver); } @Override 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 c32f0e8090ac..261258a58914 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.broadcast.BroadcastDispatcher 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 @@ constructor( 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 @@ constructor( 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 43258972ea34..1449e535c279 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.NtnSignalStrengthCallback 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.log.core.MessagePrinter 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 @@ constructor( @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 @@ constructor( } .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 eb09e6ef39cb..a97298527e11 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 @@ package com.android.systemui.statusbar.policy 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 @@ class AvalancheController @Inject constructor( dumpManager: DumpManager, + private val uiEventLogger: UiEventLogger ) : Dumpable { private val tag = "AvalancheController" @@ -65,6 +68,21 @@ constructor( // 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 @@ constructor( 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 @@ constructor( private fun showNow(entry: HeadsUpEntry, runnableList: MutableList<Runnable>) { log { "SHOW: " + getKey(entry) } + uiEventLogger.log(ThrottleEvent.SHOWN) headsUpEntryShowing = entry runnableList.forEach { @@ -295,6 +315,12 @@ constructor( // 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/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index 4bd868179faf..fad5df827ffa 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -138,7 +138,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { } } }; - globalSettings.registerContentObserver( + globalSettings.registerContentObserverSync( globalSettings.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), /* notifyForDescendants = */ false, settingsObserver); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt index 8b63dfe28f08..8123f8f3bd3d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceProvisionedControllerImpl.kt @@ -42,16 +42,16 @@ import java.util.concurrent.atomic.AtomicBoolean import javax.inject.Inject @SysUISingleton -open class DeviceProvisionedControllerImpl @Inject constructor( +open class DeviceProvisionedControllerImpl +@Inject +constructor( private val secureSettings: SecureSettings, private val globalSettings: GlobalSettings, private val userTracker: UserTracker, private val dumpManager: DumpManager, @Background private val backgroundHandler: Handler, @Main private val mainExecutor: Executor -) : DeviceProvisionedController, - DeviceProvisionedController.DeviceProvisionedListener, - Dumpable { +) : DeviceProvisionedController, DeviceProvisionedController.DeviceProvisionedListener, Dumpable { companion object { private const val ALL_USERS = -1 @@ -63,8 +63,7 @@ open class DeviceProvisionedControllerImpl @Inject constructor( private val userSetupUri = secureSettings.getUriFor(Settings.Secure.USER_SETUP_COMPLETE) private val deviceProvisioned = AtomicBoolean(false) - @GuardedBy("lock") - private val userSetupComplete = SparseBooleanArray() + @GuardedBy("lock") private val userSetupComplete = SparseBooleanArray() @GuardedBy("lock") private val listeners = ArraySet<DeviceProvisionedController.DeviceProvisionedListener>() @@ -81,42 +80,42 @@ open class DeviceProvisionedControllerImpl @Inject constructor( return _currentUser } - private val observer = object : ContentObserver(backgroundHandler) { - override fun onChange( - selfChange: Boolean, - uris: MutableCollection<Uri>, - flags: Int, - userId: Int - ) { - val updateDeviceProvisioned = deviceProvisionedUri in uris - val updateUser = if (userSetupUri in uris) userId else NO_USERS - updateValues(updateDeviceProvisioned, updateUser) - if (updateDeviceProvisioned) { - onDeviceProvisionedChanged() - } - if (updateUser != NO_USERS) { - onUserSetupChanged() + private val observer = + object : ContentObserver(backgroundHandler) { + override fun onChange( + selfChange: Boolean, + uris: MutableCollection<Uri>, + flags: Int, + userId: Int + ) { + val updateDeviceProvisioned = deviceProvisionedUri in uris + val updateUser = if (userSetupUri in uris) userId else NO_USERS + updateValues(updateDeviceProvisioned, updateUser) + if (updateDeviceProvisioned) { + onDeviceProvisionedChanged() + } + if (updateUser != NO_USERS) { + onUserSetupChanged() + } } } - } - private val userChangedCallback = object : UserTracker.Callback { - @WorkerThread - override fun onUserChanged(newUser: Int, userContext: Context) { - updateValues(updateDeviceProvisioned = false, updateUser = newUser) - onUserSwitched() - } + private val userChangedCallback = + object : UserTracker.Callback { + @WorkerThread + override fun onUserChanged(newUser: Int, userContext: Context) { + updateValues(updateDeviceProvisioned = false, updateUser = newUser) + onUserSwitched() + } - override fun onProfilesChanged(profiles: List<UserInfo>) {} - } + override fun onProfilesChanged(profiles: List<UserInfo>) {} + } init { userSetupComplete.put(currentUser, false) } - /** - * Call to initialize values and register observers - */ + /** Call to initialize values and register observers */ open fun init() { if (!initted.compareAndSet(false, true)) { return @@ -124,31 +123,39 @@ open class DeviceProvisionedControllerImpl @Inject constructor( dumpManager.registerDumpable(this) updateValues() userTracker.addCallback(userChangedCallback, backgroundExecutor) - globalSettings.registerContentObserver(deviceProvisionedUri, observer) - secureSettings.registerContentObserverForUser(userSetupUri, observer, UserHandle.USER_ALL) + globalSettings.registerContentObserverSync(deviceProvisionedUri, observer) + secureSettings.registerContentObserverForUserSync( + userSetupUri, + observer, + UserHandle.USER_ALL + ) } @WorkerThread - private fun updateValues( - updateDeviceProvisioned: Boolean = true, - updateUser: Int = ALL_USERS - ) { + private fun updateValues(updateDeviceProvisioned: Boolean = true, updateUser: Int = ALL_USERS) { if (updateDeviceProvisioned) { - deviceProvisioned - .set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0) + deviceProvisioned.set(globalSettings.getInt(Settings.Global.DEVICE_PROVISIONED, 0) != 0) } synchronized(lock) { if (updateUser == ALL_USERS) { val n = userSetupComplete.size() for (i in 0 until n) { val user = userSetupComplete.keyAt(i) - val value = secureSettings - .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0 + val value = + secureSettings.getIntForUser( + Settings.Secure.USER_SETUP_COMPLETE, + 0, + user + ) != 0 userSetupComplete.put(user, value) } } else if (updateUser != NO_USERS) { - val value = secureSettings - .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, updateUser) != 0 + val value = + secureSettings.getIntForUser( + Settings.Secure.USER_SETUP_COMPLETE, + 0, + updateUser + ) != 0 userSetupComplete.put(updateUser, value) } } @@ -160,15 +167,11 @@ open class DeviceProvisionedControllerImpl @Inject constructor( * The listener will not be called when this happens. */ override fun addCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) { - synchronized(lock) { - listeners.add(listener) - } + synchronized(lock) { listeners.add(listener) } } override fun removeCallback(listener: DeviceProvisionedController.DeviceProvisionedListener) { - synchronized(lock) { - listeners.remove(listener) - } + synchronized(lock) { listeners.remove(listener) } } override fun isDeviceProvisioned(): Boolean { @@ -176,20 +179,14 @@ open class DeviceProvisionedControllerImpl @Inject constructor( } override fun isUserSetup(user: Int): Boolean { - val index = synchronized(lock) { - userSetupComplete.indexOfKey(user) - } + val index = synchronized(lock) { userSetupComplete.indexOfKey(user) } return if (index < 0) { - val value = secureSettings - .getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0 - synchronized(lock) { - userSetupComplete.put(user, value) - } + val value = + secureSettings.getIntForUser(Settings.Secure.USER_SETUP_COMPLETE, 0, user) != 0 + synchronized(lock) { userSetupComplete.put(user, value) } value } else { - synchronized(lock) { - userSetupComplete.get(user, false) - } + synchronized(lock) { userSetupComplete.get(user, false) } } } @@ -214,12 +211,8 @@ open class DeviceProvisionedControllerImpl @Inject constructor( protected fun dispatchChange( callback: DeviceProvisionedController.DeviceProvisionedListener.() -> Unit ) { - val listenersCopy = synchronized(lock) { - ArrayList(listeners) - } - mainExecutor.execute { - listenersCopy.forEach(callback) - } + val listenersCopy = synchronized(lock) { ArrayList(listeners) } + mainExecutor.execute { listenersCopy.forEach(callback) } } override fun dump(pw: PrintWriter, args: Array<out String>) { @@ -229,4 +222,4 @@ open class DeviceProvisionedControllerImpl @Inject constructor( pw.println("Listeners: $listeners") } } -}
\ No newline at end of file +} 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 b07aa81de0a2..d210e93e36f1 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 @@ package com.android.systemui.statusbar.policy; 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 @@ public interface KeyguardStateController extends CallbackController<Callback> { 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 886010ccbf16..c256e6430af9 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.internal.widget.LockPatternUtils; 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 @@ import javax.inject.Inject; * */ @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/statusbar/policy/LocationControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java index 9eee5d00f708..f57b69639677 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/LocationControllerImpl.java @@ -115,7 +115,7 @@ public class LocationControllerImpl extends BroadcastReceiver implements Locatio }; // Register to listen for changes in Settings.Secure settings. - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.LOCATION_SHOW_SYSTEM_OPS, mContentObserver, UserHandle.USER_ALL); // Register to listen for changes in DeviceConfig settings. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java index 40bb67f2e746..9ab8175e13b8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java @@ -188,7 +188,7 @@ public class SensitiveNotificationProtectionControllerImpl }); } }; - settings.registerContentObserver( + settings.registerContentObserverSync( DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS, developerOptionsObserver); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java index 600005b97610..e09e5777a9a3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ZenModeControllerImpl.java @@ -140,13 +140,13 @@ public class ZenModeControllerImpl implements ZenModeController, Dumpable { mNoMan = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); if (registerZenModeContentObserverBackground()) { bgHandler.post(() -> { - globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver); - globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG, + globalSettings.registerContentObserverSync(Global.ZEN_MODE, modeContentObserver); + globalSettings.registerContentObserverSync(Global.ZEN_MODE_CONFIG_ETAG, configContentObserver); }); } else { - globalSettings.registerContentObserver(Global.ZEN_MODE, modeContentObserver); - globalSettings.registerContentObserver(Global.ZEN_MODE_CONFIG_ETAG, + globalSettings.registerContentObserverSync(Global.ZEN_MODE, modeContentObserver); + globalSettings.registerContentObserverSync(Global.ZEN_MODE_CONFIG_ETAG, configContentObserver); } updateZenMode(getModeSettingValueFromProvider()); diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java index 7494649294f5..4963aae08ab7 100644 --- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java @@ -444,7 +444,7 @@ public class ThemeOverlayController implements CoreStartable, Dumpable { filter.addAction(Intent.ACTION_WALLPAPER_CHANGED); mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mMainExecutor, UserHandle.ALL); - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, false, new ContentObserver(mBgHandler) { 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 2cad8442e3ba..000000000000 --- 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 000000000000..a7abb6b5f1d3 --- /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 000000000000..8c8c686dddcc --- /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/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt index ec89610ce014..ed522331baef 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt @@ -37,7 +37,6 @@ import android.provider.Settings.SettingNotFoundException interface SettingsProxy { /** Returns the [ContentResolver] this instance was constructed with. */ fun getContentResolver(): ContentResolver - /** * Construct the content URI for a particular name/value pair, useful for monitoring changes * with a ContentObserver. @@ -46,42 +45,36 @@ interface SettingsProxy { * @return the corresponding content URI, or null if not present */ fun getUriFor(name: String): Uri - /** * Convenience wrapper around [ContentResolver.registerContentObserver].' * * Implicitly calls [getUriFor] on the passed in name. */ - fun registerContentObserver(name: String, settingsObserver: ContentObserver) { - registerContentObserver(getUriFor(name), settingsObserver) + fun registerContentObserverSync(name: String, settingsObserver: ContentObserver) { + registerContentObserverSync(getUriFor(name), settingsObserver) } - /** Convenience wrapper around [ContentResolver.registerContentObserver].' */ - fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) = - registerContentObserver(uri, false, settingsObserver) - + fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) = + registerContentObserverSync(uri, false, settingsObserver) /** * Convenience wrapper around [ContentResolver.registerContentObserver].' * * Implicitly calls [getUriFor] on the passed in name. */ - fun registerContentObserver( + fun registerContentObserverSync( name: String, notifyForDescendants: Boolean, settingsObserver: ContentObserver - ) = registerContentObserver(getUriFor(name), notifyForDescendants, settingsObserver) - + ) = registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) /** Convenience wrapper around [ContentResolver.registerContentObserver].' */ - fun registerContentObserver( + fun registerContentObserverSync( uri: Uri, notifyForDescendants: Boolean, settingsObserver: ContentObserver ) = getContentResolver().registerContentObserver(uri, notifyForDescendants, settingsObserver) - /** See [ContentResolver.unregisterContentObserver]. */ - fun unregisterContentObserver(settingsObserver: ContentObserver) = + fun unregisterContentObserverSync(settingsObserver: ContentObserver) = getContentResolver().unregisterContentObserver(settingsObserver) - /** * Look up a name in the database. * @@ -89,7 +82,6 @@ interface SettingsProxy { * @return the corresponding value, or null if not present */ fun getString(name: String): String - /** * Store a name/value pair into the database. * @@ -98,7 +90,6 @@ interface SettingsProxy { * @return true if the value was set, false on database errors */ fun putString(name: String, value: String): Boolean - /** * Store a name/value pair into the database. * @@ -129,7 +120,6 @@ interface SettingsProxy { * @see .resetToDefaults */ fun putString(name: String, value: String, tag: String, makeDefault: Boolean): Boolean - /** * Convenience function for retrieving a single secure settings value as an integer. Note that * internally setting values are always stored as strings; this function converts the string to @@ -148,7 +138,6 @@ interface SettingsProxy { def } } - /** * Convenience function for retrieving a single secure settings value as an integer. Note that * internally setting values are always stored as strings; this function converts the string to @@ -171,7 +160,6 @@ interface SettingsProxy { throw SettingNotFoundException(name) } } - /** * Convenience function for updating a single settings value as an integer. This will either * create a new entry in the table if the given name does not exist, or modify the value of the @@ -185,7 +173,6 @@ interface SettingsProxy { fun putInt(name: String, value: Int): Boolean { return putString(name, value.toString()) } - /** * Convenience function for retrieving a single secure settings value as a boolean. Note that * internally setting values are always stored as strings; this function converts the string to @@ -199,7 +186,6 @@ interface SettingsProxy { fun getBool(name: String, def: Boolean): Boolean { return getInt(name, if (def) 1 else 0) != 0 } - /** * Convenience function for retrieving a single secure settings value as a boolean. Note that * internally setting values are always stored as strings; this function converts the string to @@ -217,7 +203,6 @@ interface SettingsProxy { fun getBool(name: String): Boolean { return getInt(name) != 0 } - /** * Convenience function for updating a single settings value as a boolean. This will either * create a new entry in the table if the given name does not exist, or modify the value of the @@ -231,7 +216,6 @@ interface SettingsProxy { fun putBool(name: String, value: Boolean): Boolean { return putInt(name, if (value) 1 else 0) } - /** * Convenience function for retrieving a single secure settings value as a `long`. Note that * internally setting values are always stored as strings; this function converts the string to @@ -246,7 +230,6 @@ interface SettingsProxy { val valString = getString(name) return parseLongOrUseDefault(valString, def) } - /** * Convenience function for retrieving a single secure settings value as a `long`. Note that * internally setting values are always stored as strings; this function converts the string to @@ -265,7 +248,6 @@ interface SettingsProxy { val valString = getString(name) return parseLongOrThrow(name, valString) } - /** * Convenience function for updating a secure settings value as a long integer. This will either * create a new entry in the table if the given name does not exist, or modify the value of the @@ -279,7 +261,6 @@ interface SettingsProxy { fun putLong(name: String, value: Long): Boolean { return putString(name, value.toString()) } - /** * Convenience function for retrieving a single secure settings value as a floating point * number. Note that internally setting values are always stored as strings; this function @@ -294,7 +275,6 @@ interface SettingsProxy { val v = getString(name) return parseFloat(v, def) } - /** * Convenience function for retrieving a single secure settings value as a float. Note that * internally setting values are always stored as strings; this function converts the string to @@ -313,7 +293,6 @@ interface SettingsProxy { val v = getString(name) return parseFloatOrThrow(name, v) } - /** * Convenience function for updating a single settings value as a floating point number. This * will either create a new entry in the table if the given name does not exist, or modify the @@ -327,7 +306,6 @@ interface SettingsProxy { fun putFloat(name: String, value: Float): Boolean { return putString(name, value.toString()) } - companion object { /** Convert a string to a long, or uses a default if the string is malformed or null */ @JvmStatic @@ -341,7 +319,6 @@ interface SettingsProxy { } return value } - /** Convert a string to a long, or throws an exception if the string is malformed or null */ @JvmStatic @Throws(SettingNotFoundException::class) @@ -355,7 +332,6 @@ interface SettingsProxy { throw SettingNotFoundException(name) } } - /** Convert a string to a float, or uses a default if the string is malformed or null */ @JvmStatic fun parseFloat(v: String?, def: Float): Float { @@ -365,7 +341,6 @@ interface SettingsProxy { def } } - /** * Convert a string to a float, or throws an exception if the string is malformed or null */ diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt index 74843685893c..d757e33fcc29 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxyExt.kt @@ -39,9 +39,9 @@ object SettingsProxyExt { } } - names.forEach { name -> registerContentObserverForUser(name, observer, userId) } + names.forEach { name -> registerContentObserverForUserSync(name, observer, userId) } - awaitClose { unregisterContentObserver(observer) } + awaitClose { unregisterContentObserverSync(observer) } } } @@ -57,9 +57,9 @@ object SettingsProxyExt { } } - names.forEach { name -> registerContentObserver(name, observer) } + names.forEach { name -> registerContentObserverSync(name, observer) } - awaitClose { unregisterContentObserver(observer) } + awaitClose { unregisterContentObserverSync(observer) } } } } diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt index 2285270b0bc7..ed139434d3bd 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt @@ -41,10 +41,8 @@ import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrUse * instances, unifying setting related actions in one place. */ interface UserSettingsProxy : SettingsProxy { - /** Returns that [UserTracker] this instance was constructed with. */ val userTracker: UserTracker - /** Returns the user id for the associated [ContentResolver]. */ var userId: Int get() = getContentResolver().userId @@ -53,7 +51,6 @@ interface UserSettingsProxy : SettingsProxy { "userId cannot be set in interface, use setter from an implementation instead." ) } - /** * Returns the actual current user handle when querying with the current user. Otherwise, * returns the passed in user id. @@ -63,63 +60,57 @@ interface UserSettingsProxy : SettingsProxy { userHandle } else userTracker.userId } - - override fun registerContentObserver(uri: Uri, settingsObserver: ContentObserver) { - registerContentObserverForUser(uri, settingsObserver, userId) + override fun registerContentObserverSync(uri: Uri, settingsObserver: ContentObserver) { + registerContentObserverForUserSync(uri, settingsObserver, userId) } - /** Convenience wrapper around [ContentResolver.registerContentObserver].' */ - override fun registerContentObserver( + override fun registerContentObserverSync( uri: Uri, notifyForDescendants: Boolean, settingsObserver: ContentObserver ) { - registerContentObserverForUser(uri, notifyForDescendants, settingsObserver, userId) + registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) } - /** * Convenience wrapper around [ContentResolver.registerContentObserver] * * Implicitly calls [getUriFor] on the passed in name. */ - fun registerContentObserverForUser( + fun registerContentObserverForUserSync( name: String, settingsObserver: ContentObserver, userHandle: Int ) { - registerContentObserverForUser(getUriFor(name), settingsObserver, userHandle) + registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle) } - /** Convenience wrapper around [ContentResolver.registerContentObserver] */ - fun registerContentObserverForUser( + fun registerContentObserverForUserSync( uri: Uri, settingsObserver: ContentObserver, userHandle: Int ) { - registerContentObserverForUser(uri, false, settingsObserver, userHandle) + registerContentObserverForUserSync(uri, false, settingsObserver, userHandle) } - /** * Convenience wrapper around [ContentResolver.registerContentObserver] * * Implicitly calls [getUriFor] on the passed in name. */ - fun registerContentObserverForUser( + fun registerContentObserverForUserSync( name: String, notifyForDescendants: Boolean, settingsObserver: ContentObserver, userHandle: Int ) { - registerContentObserverForUser( + registerContentObserverForUserSync( getUriFor(name), notifyForDescendants, settingsObserver, userHandle ) } - /** Convenience wrapper around [ContentResolver.registerContentObserver] */ - fun registerContentObserverForUser( + fun registerContentObserverForUserSync( uri: Uri, notifyForDescendants: Boolean, settingsObserver: ContentObserver, @@ -136,7 +127,6 @@ interface UserSettingsProxy : SettingsProxy { Unit } } - /** * Look up a name in the database. * @@ -146,10 +136,8 @@ interface UserSettingsProxy : SettingsProxy { override fun getString(name: String): String { return getStringForUser(name, userId) } - /** See [getString]. */ fun getStringForUser(name: String, userHandle: Int): String - /** * Store a name/value pair into the database. Values written by this method will be overridden * if a restore happens in the future. @@ -162,10 +150,8 @@ interface UserSettingsProxy : SettingsProxy { override fun putString(name: String, value: String): Boolean { return putStringForUser(name, value, userId) } - /** Similar implementation to [putString] for the specified [userHandle]. */ fun putStringForUser(name: String, value: String, userHandle: Int): Boolean - /** Similar implementation to [putString] for the specified [userHandle]. */ fun putStringForUser( name: String, @@ -175,11 +161,9 @@ interface UserSettingsProxy : SettingsProxy { @UserIdInt userHandle: Int, overrideableByRestore: Boolean ): Boolean - override fun getInt(name: String, def: Int): Int { return getIntForUser(name, def, userId) } - /** Similar implementation to [getInt] for the specified [userHandle]. */ fun getIntForUser(name: String, def: Int, userHandle: Int): Int { val v = getStringForUser(name, userHandle) @@ -189,10 +173,8 @@ interface UserSettingsProxy : SettingsProxy { def } } - @Throws(SettingNotFoundException::class) override fun getInt(name: String) = getIntForUser(name, userId) - /** Similar implementation to [getInt] for the specified [userHandle]. */ @Throws(SettingNotFoundException::class) fun getIntForUser(name: String, userHandle: Int): Int { @@ -203,66 +185,52 @@ interface UserSettingsProxy : SettingsProxy { throw SettingNotFoundException(name) } } - override fun putInt(name: String, value: Int) = putIntForUser(name, value, userId) - /** Similar implementation to [getInt] for the specified [userHandle]. */ fun putIntForUser(name: String, value: Int, userHandle: Int) = putStringForUser(name, value.toString(), userHandle) - override fun getBool(name: String, def: Boolean) = getBoolForUser(name, def, userId) - /** Similar implementation to [getBool] for the specified [userHandle]. */ fun getBoolForUser(name: String, def: Boolean, userHandle: Int) = getIntForUser(name, if (def) 1 else 0, userHandle) != 0 - @Throws(SettingNotFoundException::class) override fun getBool(name: String) = getBoolForUser(name, userId) - /** Similar implementation to [getBool] for the specified [userHandle]. */ @Throws(SettingNotFoundException::class) fun getBoolForUser(name: String, userHandle: Int): Boolean { return getIntForUser(name, userHandle) != 0 } - override fun putBool(name: String, value: Boolean): Boolean { return putBoolForUser(name, value, userId) } - /** Similar implementation to [putBool] for the specified [userHandle]. */ fun putBoolForUser(name: String, value: Boolean, userHandle: Int) = putIntForUser(name, if (value) 1 else 0, userHandle) - /** Similar implementation to [getLong] for the specified [userHandle]. */ fun getLongForUser(name: String, def: Long, userHandle: Int): Long { val valString = getStringForUser(name, userHandle) return parseLongOrUseDefault(valString, def) } - /** Similar implementation to [getLong] for the specified [userHandle]. */ @Throws(SettingNotFoundException::class) fun getLongForUser(name: String, userHandle: Int): Long { val valString = getStringForUser(name, userHandle) return parseLongOrThrow(name, valString) } - /** Similar implementation to [putLong] for the specified [userHandle]. */ fun putLongForUser(name: String, value: Long, userHandle: Int) = putStringForUser(name, value.toString(), userHandle) - /** Similar implementation to [getFloat] for the specified [userHandle]. */ fun getFloatForUser(name: String, def: Float, userHandle: Int): Float { val v = getStringForUser(name, userHandle) return parseFloat(v, def) } - /** Similar implementation to [getFloat] for the specified [userHandle]. */ @Throws(SettingNotFoundException::class) fun getFloatForUser(name: String, userHandle: Int): Float { val v = getStringForUser(name, userHandle) return parseFloatOrThrow(name, v) } - /** Similar implementation to [putFloat] for the specified [userHandle]. */ fun putFloatForUser(name: String, value: Float, userHandle: Int) = putStringForUser(name, value.toString(), userHandle) 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 c6b0dc542087..154737c71b9e 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.CoroutineScope 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 @@ import kotlinx.coroutines.flow.stateIn class AudioOutputInteractor @Inject constructor( + @Application private val context: Context, audioRepository: AudioRepository, audioModeInteractor: AudioModeInteractor, @VolumePanelScope scope: CoroutineScope, @@ -60,7 +63,7 @@ constructor( audioSharingRepository: AudioSharingRepository, ) { - val currentAudioDevice: Flow<AudioOutputDevice> = + val currentAudioDevice: StateFlow<AudioOutputDevice> = audioModeInteractor.isOngoingCall .flatMapLatest { isOngoingCall -> if (isOngoingCall) { @@ -81,30 +84,32 @@ constructor( val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing private fun AudioDeviceInfo.toAudioOutputDevice(): AudioOutputDevice { - if (type == TYPE_WIRED_HEADPHONES || type == TYPE_WIRED_HEADSET) { - 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) + 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, + ) } - return cachedBluetoothDevice?.let { - AudioOutputDevice.Bluetooth( - name = it.name, - icon = deviceIconInteractor.loadIcon(it), - cachedBluetoothDevice = it, - ) } - ?: AudioOutputDevice.BuiltIn( + // Built-in device has an empty address + if (address.isNotEmpty()) { + return AudioOutputDevice.Wired( 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 @@ constructor( 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 0dc264781070..79a4ae74b217 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.LocalMediaRepository 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 @@ class LocalMediaRepositoryFactoryImpl 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 199bc3b78dd2..333f4ad088b8 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.DialogCuj 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 @@ import javax.inject.Inject @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 000000000000..ed25129ff082 --- /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 9fbd79accf80..31a89775e916 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 javax.inject.Inject 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.map 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 @@ constructor( .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 000000000000..220fb2b42eb8 --- /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 40b797778c2b..36b42f28e202 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 @@ package com.android.systemui.volume.panel.component.mediaoutput.ui.viewmodel 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 @@ constructor( 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) - } - ConnectedDeviceViewModel( - label, - when (isInAudioSharing) { - true -> { - context.getString(R.string.audio_sharing_description) + 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) + } } - false -> { - currentConnectedDevice.name + is MediaOutputComponentModel.Calling -> { + context.getString(R.string.media_output_title_ongoing_call) } } + ConnectedDeviceViewModel( + label, + if (mediaOutputModel.isInAudioSharing) { + context.getString(R.string.audio_sharing_description) + } else { + mediaOutputModel.device.name + }, ) } .stateIn( @@ -120,16 +90,20 @@ constructor( ) 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 @@ constructor( ) val enabled: StateFlow<Boolean> = - audioOutputInteractor.isInAudioSharing - .map { !it } + mediaOutputComponentInteractor.mediaOutputModel + .filterData() + .map { !it.isInAudioSharing } .stateIn( coroutineScope, SharingStarted.Eagerly, @@ -166,7 +141,11 @@ constructor( 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 4b2d26a6b28b..6c6a1cca67f3 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 @@ constructor( 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 @@ constructor( } } .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 @@ constructor( 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 @@ constructor( 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 @@ constructor( 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 fd01b4864772..850162e65aa6 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 @@ constructor( 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 99f956489bc3..3da725b9a51f 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 @@ constructor( private fun showNewVolumePanel() { activityStarter.dismissKeyguardThenExecute( - { + /* action = */ { volumePanelGlobalStateInteractor.setVisible(true) false }, - {}, - true + /* cancel = */ {}, + /* afterKeyguardGone = */ true, ) } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java index ff1841819dea..1d32a4fd69d6 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java @@ -149,12 +149,12 @@ public class QuickAccessWalletController { if (event == WALLET_PREFERENCE_CHANGE && mWalletPreferenceObserver != null) { mWalletPreferenceChangeEvents--; if (mWalletPreferenceChangeEvents == 0) { - mSecureSettings.unregisterContentObserver(mWalletPreferenceObserver); + mSecureSettings.unregisterContentObserverSync(mWalletPreferenceObserver); } } else if (event == DEFAULT_PAYMENT_APP_CHANGE && mDefaultPaymentAppObserver != null) { mDefaultPaymentAppChangeEvents--; if (mDefaultPaymentAppChangeEvents == 0) { - mSecureSettings.unregisterContentObserver(mDefaultPaymentAppObserver); + mSecureSettings.unregisterContentObserverSync(mDefaultPaymentAppObserver); } } else if (event == DEFAULT_WALLET_APP_CHANGE && mDefaultWalletAppObserver != null) { mDefaultWalletAppChangeEvents--; @@ -312,7 +312,7 @@ public class QuickAccessWalletController { } }; - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT, false /* notifyForDescendants */, mDefaultPaymentAppObserver, @@ -351,7 +351,7 @@ public class QuickAccessWalletController { } }; - mSecureSettings.registerContentObserverForUser( + mSecureSettings.registerContentObserverForUserSync( QuickAccessWalletClientImpl.SETTING_KEY, false /* notifyForDescendants */, mWalletPreferenceObserver, diff --git a/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/CarrierTextManagerTest.java index 8c4179d385ba..0a3225eecbe4 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 @@ public class CarrierTextManagerTest extends SysuiTestCase { 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 @@ public class CarrierTextManagerTest extends SysuiTestCase { } @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 @@ public class CarrierTextManagerTest extends SysuiTestCase { 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 @@ public class CarrierTextManagerTest extends SysuiTestCase { 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 @@ public class CarrierTextManagerTest extends SysuiTestCase { // 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 @@ public class CarrierTextManagerTest extends SysuiTestCase { 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/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java index 99b5a4b631a7..cfa74f0ef432 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java @@ -181,7 +181,7 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro ArgumentCaptor.forClass(ContentObserver.class); mController.init(); mExecutor.runAllReady(); - verify(mSecureSettings).registerContentObserverForUser( + verify(mSecureSettings).registerContentObserverForUserSync( eq(Settings.Secure.LOCKSCREEN_USE_DOUBLE_LINE_CLOCK), anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL)); ContentObserver observer = observerCaptor.getValue(); @@ -247,7 +247,7 @@ public class KeyguardClockSwitchControllerTest extends KeyguardClockSwitchContro ArgumentCaptor.forClass(ContentObserver.class); mController.init(); mExecutor.runAllReady(); - verify(mSecureSettings).registerContentObserverForUser( + verify(mSecureSettings).registerContentObserverForUserSync( eq(Settings.Secure.LOCK_SCREEN_WEATHER_ENABLED), anyBoolean(), observerCaptor.capture(), eq(UserHandle.USER_ALL)); ContentObserver observer = observerCaptor.getValue(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java index 92b06ba3f714..138fed298ef1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java @@ -393,7 +393,7 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase { mWindowMagnificationSettings.showSettingPanel(); - verify(mSecureSettings).registerContentObserverForUser( + verify(mSecureSettings).registerContentObserverForUserSync( eq(ACCESSIBILITY_MAGNIFICATION_CAPABILITY), any(ContentObserver.class), eq(UserHandle.USER_CURRENT)); @@ -408,7 +408,7 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase { mWindowMagnificationSettings.showSettingPanel(); mWindowMagnificationSettings.hideSettingPanel(); - verify(mSecureSettings).unregisterContentObserver(any(ContentObserver.class)); + verify(mSecureSettings).unregisterContentObserverSync(any(ContentObserver.class)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt index 63426a43e552..9a99ed7bb512 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt @@ -128,6 +128,12 @@ open class AuthContainerViewTest : SysuiTestCase() { private lateinit var packageManager: PackageManager @Mock private lateinit var activityTaskManager: ActivityTaskManager + private lateinit var displayRepository: FakeDisplayRepository + private lateinit var displayStateInteractor: DisplayStateInteractor + private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor + private lateinit var biometricStatusInteractor: BiometricStatusInteractor + private lateinit var iconProvider: IconProvider + private val testScope = TestScope(StandardTestDispatcher()) private val fakeExecutor = FakeExecutor(FakeSystemClock()) private val biometricPromptRepository = FakePromptRepository() @@ -143,17 +149,12 @@ open class AuthContainerViewTest : SysuiTestCase() { private val promptSelectorInteractor by lazy { PromptSelectorInteractorImpl( fingerprintRepository, + displayStateInteractor, biometricPromptRepository, lockPatternUtils, ) } - private lateinit var displayRepository: FakeDisplayRepository - private lateinit var displayStateInteractor: DisplayStateInteractor - private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor - private lateinit var biometricStatusInteractor: BiometricStatusInteractor - private lateinit var iconProvider: IconProvider - private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor) private val defaultLogoIcon = context.getDrawable(R.drawable.ic_android) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt index 9ba56d27fdf3..63919865d8ab 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FaceSettingsRepositoryImplTest.kt @@ -82,7 +82,12 @@ class FaceSettingsRepositoryImplTest : SysuiTestCase() { val keys = captureMany<String> { verify(secureSettings) - .registerContentObserverForUser(capture(), anyBoolean(), any(), eq(USER_ID)) + .registerContentObserverForUserSync( + capture(), + anyBoolean(), + any(), + eq(USER_ID) + ) } assertThat(keys).containsExactly(FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION) @@ -102,7 +107,7 @@ class FaceSettingsRepositoryImplTest : SysuiTestCase() { val observer = withArgCaptor<ContentObserver> { verify(secureSettings) - .registerContentObserverForUser( + .registerContentObserverForUserSync( eq(setting), anyBoolean(), capture(), diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt index 3102a84b852a..6e78e334891b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt @@ -25,13 +25,16 @@ import android.hardware.biometrics.PromptVerticalListContentView import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase +import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.biometrics.data.repository.FakePromptRepository import com.android.systemui.biometrics.faceSensorPropertiesInternal import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal import com.android.systemui.biometrics.shared.model.BiometricModalities +import com.android.systemui.biometrics.shared.model.DisplayRotation import com.android.systemui.biometrics.shared.model.PromptKind import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.display.data.repository.FakeDisplayRepository import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever @@ -75,12 +78,30 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { private val promptRepository = FakePromptRepository() private val fakeExecutor = FakeExecutor(FakeSystemClock()) + private lateinit var displayStateRepository: FakeDisplayStateRepository + private lateinit var displayRepository: FakeDisplayRepository + private lateinit var displayStateInteractor: DisplayStateInteractor private lateinit var interactor: PromptSelectorInteractor @Before fun setup() { + displayStateRepository = FakeDisplayStateRepository() + displayRepository = FakeDisplayRepository() + displayStateInteractor = + DisplayStateInteractorImpl( + testScope.backgroundScope, + mContext, + fakeExecutor, + displayStateRepository, + displayRepository, + ) interactor = - PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) + PromptSelectorInteractorImpl( + fingerprintRepository, + displayStateInteractor, + promptRepository, + lockPatternUtils + ) } private fun basicPromptInfo() = @@ -155,7 +176,8 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { modalities, CHALLENGE, OP_PACKAGE_NAME, - false /*onSwitchToCredential*/ + onSwitchToCredential = false, + isLandscape = false, ) assertThat(currentPrompt).isNotNull() @@ -200,22 +222,49 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { fun promptKind_isBiometric_whenBiometricAllowed() = testScope.runTest { setUserCredentialType(isPassword = true) - val info = basicPromptInfo() val promptKind by collectLastValue(interactor.promptKind) assertThat(promptKind).isEqualTo(PromptKind.None) - interactor.setPrompt( - info, - USER_ID, - REQUEST_ID, - modalities, - CHALLENGE, - OP_PACKAGE_NAME, - false /*onSwitchToCredential*/ - ) + setPrompt() + + assertThat(promptKind?.isOnePanePortraitBiometric()).isTrue() + + interactor.resetPrompt(REQUEST_ID) + verifyUnset() + } + + @Test + fun promptKind_isBiometricTwoPane_whenBiometricAllowed_landscape() = + testScope.runTest { + setUserCredentialType(isPassword = true) + displayStateRepository.setIsLargeScreen(false) + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) + + setPrompt() + + assertThat(promptKind?.isTwoPaneLandscapeBiometric()).isTrue() + + interactor.resetPrompt(REQUEST_ID) + verifyUnset() + } + + @Test + fun promptKind_isBiometricOnePane_whenBiometricAllowed_largeScreenLandscape() = + testScope.runTest { + setUserCredentialType(isPassword = true) + displayStateRepository.setIsLargeScreen(true) + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) + + val promptKind by collectLastValue(interactor.promptKind) + assertThat(promptKind).isEqualTo(PromptKind.None) - assertThat(promptKind?.isBiometric()).isTrue() + setPrompt() + + assertThat(promptKind?.isOnePaneLargeScreenLandscapeBiometric()).isTrue() interactor.resetPrompt(REQUEST_ID) verifyUnset() @@ -225,20 +274,11 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { fun promptKind_isCredential_onSwitchToCredential() = testScope.runTest { setUserCredentialType(isPassword = true) - val info = basicPromptInfo() val promptKind by collectLastValue(interactor.promptKind) assertThat(promptKind).isEqualTo(PromptKind.None) - interactor.setPrompt( - info, - USER_ID, - REQUEST_ID, - modalities, - CHALLENGE, - OP_PACKAGE_NAME, - true /*onSwitchToCredential*/ - ) + setPrompt(onSwitchToCredential = true) assertThat(promptKind).isEqualTo(PromptKind.Password) @@ -259,15 +299,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { val promptKind by collectLastValue(interactor.promptKind) assertThat(promptKind).isEqualTo(PromptKind.None) - interactor.setPrompt( - info, - USER_ID, - REQUEST_ID, - modalities, - CHALLENGE, - OP_PACKAGE_NAME, - false /*onSwitchToCredential*/ - ) + setPrompt(info) assertThat(promptKind).isEqualTo(PromptKind.Password) @@ -292,15 +324,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { val promptKind by collectLastValue(interactor.promptKind) assertThat(promptKind).isEqualTo(PromptKind.None) - interactor.setPrompt( - info, - USER_ID, - REQUEST_ID, - modalities, - CHALLENGE, - OP_PACKAGE_NAME, - false /*onSwitchToCredential*/ - ) + setPrompt(info) assertThat(promptKind).isEqualTo(PromptKind.Password) @@ -312,6 +336,7 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { fun promptKind_isBiometric_whenBiometricIsNotAllowed_withVerticalList() = testScope.runTest { setUserCredentialType(isPassword = true) + displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90) val info = basicPromptInfo().apply { isDeviceCredentialAllowed = true @@ -322,22 +347,32 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { val promptKind by collectLastValue(interactor.promptKind) assertThat(promptKind).isEqualTo(PromptKind.None) - interactor.setPrompt( - info, - USER_ID, - REQUEST_ID, - modalities, - CHALLENGE, - OP_PACKAGE_NAME, - false /*onSwitchToCredential*/ - ) + setPrompt(info) - assertThat(promptKind?.isBiometric()).isTrue() + assertThat(promptKind?.isOnePaneNoSensorLandscapeBiometric()).isTrue() interactor.resetPrompt(REQUEST_ID) verifyUnset() } + private fun setPrompt( + info: PromptInfo = basicPromptInfo(), + onSwitchToCredential: Boolean = false + ) { + interactor.setPrompt( + info, + USER_ID, + REQUEST_ID, + modalities, + CHALLENGE, + OP_PACKAGE_NAME, + onSwitchToCredential = onSwitchToCredential, + isLandscape = + displayStateRepository.currentRotation.value == DisplayRotation.ROTATION_90 || + displayStateRepository.currentRotation.value == DisplayRotation.ROTATION_270, + ) + } + private fun TestScope.useCredentialAndReset(kind: PromptKind) { setUserCredentialType( isPin = kind == PromptKind.Pin, @@ -366,7 +401,8 @@ class PromptSelectorInteractorImplTest : SysuiTestCase() { BiometricModalities(), CHALLENGE, OP_PACKAGE_NAME, - false /*onSwitchToCredential*/ + onSwitchToCredential = false, + isLandscape = false, ) // not using biometrics, should be null with no fallback option 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 c177511a2df4..f46cfdc280fe 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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } 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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } } 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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa } 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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa 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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa // 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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa ) 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 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa 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 @@ -1407,7 +1457,12 @@ internal class PromptViewModelTest(private val testCase: TestCase) : SysuiTestCa runningTaskInfo.topActivity = topActivity whenever(activityTaskManager.getTasks(1)).thenReturn(listOf(runningTaskInfo)) selector = - PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils) + PromptSelectorInteractorImpl( + fingerprintRepository, + displayStateInteractor, + promptRepository, + lockPatternUtils + ) selector.resetPrompt(REQUEST_ID) viewModel = @@ -1643,7 +1698,8 @@ private fun PromptSelectorInteractor.initializePrompt( BiometricModalities(fingerprintProperties = fingerprint, faceProperties = face), CHALLENGE, packageName, - false /*onUseDeviceCredential*/ + onSwitchToCredential = false, + isLandscape = false, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt index 590989d3a987..154c3734558d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/controls/settings/ControlsSettingsDialogManagerImplTest.kt @@ -332,7 +332,7 @@ class ControlsSettingsDialogManagerImplTest : SysuiTestCase() { } private fun attachRepositoryToSettings() { - secureSettings.registerContentObserver( + secureSettings.registerContentObserverSync( SETTING_SHOW, object : ContentObserver(null) { override fun onChange(selfChange: Boolean) { @@ -343,7 +343,7 @@ class ControlsSettingsDialogManagerImplTest : SysuiTestCase() { } ) - secureSettings.registerContentObserver( + secureSettings.registerContentObserverSync( SETTING_ACTION, object : ContentObserver(null) { override fun onChange(selfChange: Boolean) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java index cfe37eef1f87..e2cca3873bf7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java @@ -42,7 +42,6 @@ import android.os.Handler; import android.os.UserManager; import android.provider.Settings; import android.service.dreams.IDreamManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.GestureDetector; import android.view.IWindowManager; @@ -52,6 +51,7 @@ import android.view.WindowManagerPolicyConstants; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedDispatcher; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.FlakyTest; import androidx.test.filters.SmallTest; @@ -99,7 +99,7 @@ import java.util.List; import java.util.concurrent.Executor; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class GlobalActionsDialogLiteTest extends SysuiTestCase { private GlobalActionsDialogLite mGlobalActionsDialogLite; 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 84b1c00f29e6..87dd9b229598 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 junit.framework.Assert.assertEquals; 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 @@ import org.junit.runner.RunWith; * 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 16dcd659b3f9..ea0f4d247a47 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.doReturn; 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 @@ import java.util.ArrayList; * 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 4c65b904bbc1..a10457f704ec 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 @@ package com.android.systemui.globalactions; 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 @@ import org.junit.runner.RunWith; * 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 28c01ad3a043..73509e2da520 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.net.platform.flags.Flags; 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.Mock; 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 d7a0d5b5e062..64cd09128373 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 kotlinx.coroutines.test.runTest 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 7207fbf83bd7..47261a935725 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 kotlinx.coroutines.test.runTest 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 @@ import org.mockito.MockitoAnnotations @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 4410e68e64aa..53bcf865b829 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 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 kotlinx.coroutines.test.runTest 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 @@ import org.mockito.MockitoAnnotations @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/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt new file mode 100644 index 000000000000..3a868685f4d9 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/keyboard/docking/ui/viewmodel/KeyboardDockingIndicationViewModelTest.kt @@ -0,0 +1,99 @@ +/* + * 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.docking.ui.viewmodel + +import android.graphics.Rect +import android.view.WindowInsets +import android.view.WindowManager +import android.view.WindowMetrics +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository +import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor +import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository +import com.android.systemui.keyboard.docking.domain.interactor.KeyboardDockingIndicationInteractor +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.mockito.Mockito.spy +import org.mockito.MockitoAnnotations +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever + +@SmallTest +@RunWith(JUnit4::class) +class KeyboardDockingIndicationViewModelTest : SysuiTestCase() { + private val testScope = TestScope(StandardTestDispatcher()) + + private lateinit var keyboardRepository: FakeKeyboardRepository + private lateinit var configurationRepository: FakeConfigurationRepository + private lateinit var underTest: KeyboardDockingIndicationViewModel + private lateinit var windowManager: WindowManager + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + + keyboardRepository = FakeKeyboardRepository() + configurationRepository = FakeConfigurationRepository() + windowManager = spy(context.getSystemService(WindowManager::class.java)!!) + + val keyboardDockingIndicationInteractor = + KeyboardDockingIndicationInteractor(keyboardRepository) + val configurationInteractor = ConfigurationInteractor(configurationRepository) + + underTest = + KeyboardDockingIndicationViewModel( + windowManager, + context, + keyboardDockingIndicationInteractor, + configurationInteractor, + testScope.backgroundScope + ) + } + + @Test + fun onConfigurationChanged_createsNewConfig() { + val oldBounds = Rect(0, 0, 10, 10) + val newBounds = Rect(10, 10, 20, 20) + val inset = WindowInsets(Rect(1, 1, 1, 1)) + val density = 1f + + doReturn(WindowMetrics(oldBounds, inset, density)) + .whenever(windowManager) + .currentWindowMetrics + + val firstGlow = underTest.edgeGlow.value + + testScope.runTest { + configurationRepository.onAnyConfigurationChange() + // Ensure there's some change in the config so that flow emits the new value. + doReturn(WindowMetrics(newBounds, inset, density)) + .whenever(windowManager) + .currentWindowMetrics + + val secondGlow = underTest.edgeGlow.value + + assertThat(firstGlow.hashCode()).isNotEqualTo(secondGlow.hashCode()) + } + } +} 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 05a2ca20037b..0757ea156bbf 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.UnconfinedTestDispatcher 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 44a8904f50da..34b2aaf8b57a 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.UnconfinedTestDispatcher 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 59d8fc3d3fd0..9a721fa26df3 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 kotlinx.coroutines.test.runCurrent 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 d14d72d90a31..9d9e5be62351 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 @@ package com.android.systemui.keyboard.stickykeys.ui.viewmodel 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 kotlinx.coroutines.test.runTest 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 5b836b6b06b9..925ace26871f 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 static org.mockito.Mockito.when; 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.Captor; 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 b44fb8e61c32..325bae15b18e 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.content.res.ColorStateList; 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 com.android.systemui.SysuiTestCase; 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 ce8028c8b37a..909acca12551 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.media.session.PlaybackState; 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.SliceProvider; 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.HashSet; 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 6ebda4db808f..128dd2353042 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.Point 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.SyncRtSurfaceTransactionApplier 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.Mockito.verifyNoMoreInteractions 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 977116e812ac..144e634ae519 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.data.repository.setSceneTransition 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.Mockito.verifyZeroInteractions import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ResourceTrimmerTest : SysuiTestCase() { val kosmos = testKosmos() @@ -62,52 +56,21 @@ class ResourceTrimmerTest : SysuiTestCase() { @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 @@ class ResourceTrimmerTest : SysuiTestCase() { } @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 @@ class ResourceTrimmerTest : SysuiTestCase() { } @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 @@ class ResourceTrimmerTest : SysuiTestCase() { } @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 @@ class ResourceTrimmerTest : SysuiTestCase() { } @Test - @EnableFlags(com.android.systemui.Flags.FLAG_TRIM_RESOURCES_WITH_BACKGROUND_TRIM_AT_LOCK) @DisableSceneContainer fun keyguardTransitionsToGone_trimsFontCache() = testScope.runTest { @@ -220,12 +139,10 @@ class ResourceTrimmerTest : SysuiTestCase() { ) 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 @@ class ResourceTrimmerTest : SysuiTestCase() { 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 70a0415d2e35..99ff2d405c6f 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.mock; 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 org.junit.runner.RunWith; 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 39a453da7f92..a9f7d0005624 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 static org.mockito.Mockito.verifyNoMoreInteractions; 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 org.junit.runner.RunWith; 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 c7f1decac670..d435a4708b0a 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.graphics.drawable.Drawable 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 bd4525b28f71..47bf653c699c 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 @@ package com.android.systemui.keyguard.data.quickaffordance 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 df1833e2506e..5fa194d95e28 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 @@ class MuteQuickAffordanceCoreStartableTest : SysuiTestCase() { 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 f5b5261de139..972ca02548ec 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 kotlinx.coroutines.test.runTest 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 9aac8e270f92..af5187d03261 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.TestScope 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 a320845be6e4..8b8a6cbf6148 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.TestScope 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 8be1e7a2b3f1..a8271c1b7786 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.Before 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 @@ import org.mockito.junit.MockitoRule @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 14d954873f0f..d2a9c582d904 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.StandardTestDispatcher 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 @@ import org.mockito.MockitoAnnotations 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 ced3526f40be..9d06031a3ed5 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.StandardTestDispatcher 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 @@ import org.mockito.MockitoAnnotations 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 8a0613f9b010..baa8ed73d7fb 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 java.io.PrintWriter 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.never 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 344e0fc2bc6a..9fab0d9065b6 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.never 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 b3cc5c949cb8..4a39a9b2e801 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.content.res.Resources 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 kotlinx.coroutines.test.runTest 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 4f2b690f9fcd..693a87761b27 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.graphics.Point 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 kotlinx.coroutines.test.TestScope 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 711f90f043ee..10f7128af43c 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 @@ package com.android.systemui.keyguard.ui.view.layout.sections 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 com.google.common.truth.Truth.assertThat 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 be10b82193c1..7d4f03453fa7 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.ConstraintLayout 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 kotlinx.coroutines.flow.MutableStateFlow 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 f1c93c4652c3..e44bc7b43fb1 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.ExperimentalCoroutinesApi 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 391831a61579..6398a5af52f1 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.ExperimentalCoroutinesApi 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 ec2a1d305ab3..129752e4f106 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 com.android.systemui.testKosmos 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 8a12a90efc4c..e33d75c02052 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 @@ package com.android.systemui.keyguard.ui.viewmodel 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 @@ -75,7 +76,6 @@ import kotlinx.coroutines.test.runTest 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 +83,7 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { @Mock private lateinit var activityStarter: ActivityStarter 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 78f4dccd18a3..0c3fcb3ef759 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 kotlinx.coroutines.test.runTest 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 447b333b942f..fbeb6d8d0a6f 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.verify; 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.Captor; 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 ab19b3aeceb0..d2e6dad8ed8d 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.anyString 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 5986f4a9a9aa..e2a2b7a91319 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 @@ package com.android.systemui.media.controls.domain.pipeline 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 const val SMARTSPACE_PACKAGE = "SMARTSPACE_PKG" 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/LegacyMediaDataManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt index 3372f06dec22..bdee9365d494 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/domain/pipeline/LegacyMediaDataManagerImplTest.kt @@ -40,9 +40,9 @@ import android.net.Uri import android.os.Bundle import android.provider.Settings import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.media.utils.MediaConstants +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.logging.InstanceId @@ -113,7 +113,7 @@ private fun <T> anyObject(): T { @SmallTest @RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class LegacyMediaDataManagerImplTest : SysuiTestCase() { @JvmField @Rule val mockito = MockitoJUnit.rule() 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 dd05a0d12429..544350c7e24d 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.never; 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 org.mockito.junit.MockitoRule; 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 caaa42fc364c..8471fe1ed2d2 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 @@ package com.android.systemui.media.controls.domain.pipeline 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 @@ private val SMARTSPACE_INSTANCE_ID = InstanceId.fakeInstanceId(456)!! @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 3bf4173cd7c7..18b4c48d063e 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 @@ -40,10 +40,10 @@ import android.net.Uri import android.os.Bundle import android.provider.Settings import android.service.notification.StatusBarNotification -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import androidx.media.utils.MediaConstants +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.logging.InstanceId @@ -125,7 +125,7 @@ private fun <T> anyObject(): T { @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class MediaDataProcessorTest : SysuiTestCase() { val kosmos = testKosmos() @@ -191,7 +191,7 @@ class MediaDataProcessorTest : SysuiTestCase() { @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 16d8819f13fb..42bd46f323b5 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.DisableFlags 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 BROADCAST_APP_NAME = "BROADCAST_APP_NAME" 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 31a243591b60..efe4413cfb0f 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 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 @@ private val info = 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 6ca0bef17404..c1bba4d4d60c 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.MediaMetadata 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 @@ private fun <T> anyObject(): T { } @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 55ff231ab6b6..02d741385cf9 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.content.pm.ServiceInfo 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> eq(value: T): T = Mockito.eq(value) ?: value 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 8dfa5b8e640c..dca19690fd14 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.browse.MediaBrowser 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> eq(value: T): T = Mockito.eq(value) ?: value 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 b509e779a8c3..addf008619c9 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.Mockito.mock 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 4fcd3bb7600a..cdcb143fb00c 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 @@ package com.android.systemui.media.controls.ui.animation 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.Mockito.`when` as whenever 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 aa297b537c49..4d0605fd5190 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 @@ package com.android.systemui.media.controls.ui.animation 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 DEFAULT_COLOR = Color.RED 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 bb95ba356fef..2499c9ce7791 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.Mockito.`when` as whenever 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 8a6b2722b1a2..4e14fec8408f 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 @@ package com.android.systemui.media.controls.ui.binder 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.Mockito.`when` as whenever 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 791563a839b1..2f95936a576f 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.Mockito.verify 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 a89139b18bed..f7b3f2ea2804 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.content.res.Configuration 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.flags.fakeFeatureFlagsClassic 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 @@ private const val PLAYING_LOCAL = "playing local" @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class MediaCarouselControllerTest : SysuiTestCase() { val kosmos = testKosmos() @@ -158,6 +159,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { testDispatcher = UnconfinedTestDispatcher() mediaCarouselController = MediaCarouselController( + applicationScope = kosmos.applicationCoroutineScope, context = context, mediaControlPanelFactory = mediaControlPanelFactory, visualStabilityProvider = visualStabilityProvider, @@ -194,7 +196,7 @@ class MediaCarouselControllerTest : SysuiTestCase() { whenever(mediaFlags.isPersistentSsCardEnabled()).thenReturn(false) MediaPlayerData.clear() verify(globalSettings) - .registerContentObserver( + .registerContentObserverSync( eq(Settings.Global.getUriFor(Settings.Global.ANIMATOR_DURATION_SCALE)), capture(settingsObserverCaptor) ) @@ -893,7 +895,10 @@ class MediaCarouselControllerTest : SysuiTestCase() { 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 @@ class MediaCarouselControllerTest : SysuiTestCase() { 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 6d7976e6e51d..ecc456cb3888 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.annotations.RequiresFlagsEnabled 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.Barrier 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 REC_APP_NAME = "REC APP NAME" 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 5f7c3869fee7..bba01bd0b78b 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 @@ package com.android.systemui.media.controls.ui.controller 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.communal.ui.viewmodel.communalTransitionViewModel 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.KeyguardStateController 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.times 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 @@ class MediaHierarchyManagerTest : SysuiTestCase() { 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 @@ class MediaHierarchyManagerTest : SysuiTestCase() { 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 @@ class MediaHierarchyManagerTest : SysuiTestCase() { mediaDataManager, keyguardViewController, dreamOverlayStateController, + kosmos.keyguardInteractor, kosmos.communalTransitionViewModel, configurationController, wakefulnessLifecycle, @@ -191,7 +199,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -204,7 +212,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController, times(0)) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -218,7 +226,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -231,7 +239,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController, times(0)) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -245,7 +253,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -255,7 +263,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -269,7 +277,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -281,7 +289,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( ArgumentMatchers.anyInt(), - any(MediaHostState::class.java), + any<MediaHostState>(), anyBoolean(), anyLong(), anyLong() @@ -297,7 +305,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( eq(MediaHierarchyManager.LOCATION_QQS), - any(MediaHostState::class.java), + any<MediaHostState>(), eq(false), anyLong(), anyLong() @@ -309,7 +317,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( eq(MediaHierarchyManager.LOCATION_LOCKSCREEN), - any(MediaHostState::class.java), + any<MediaHostState>(), eq(false), anyLong(), anyLong() @@ -482,6 +490,26 @@ class MediaHierarchyManagerTest : SysuiTestCase() { } @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 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( eq(MediaHierarchyManager.LOCATION_QQS), - any(MediaHostState::class.java), + any<MediaHostState>(), eq(false), anyLong(), anyLong() @@ -532,7 +560,7 @@ class MediaHierarchyManagerTest : SysuiTestCase() { verify(mediaCarouselController) .onDesiredLocationChanged( eq(MediaHierarchyManager.LOCATION_QQS), - any(MediaHostState::class.java), + any<MediaHostState>(), eq(false), anyLong(), anyLong() @@ -590,7 +618,50 @@ class MediaHierarchyManagerTest : SysuiTestCase() { 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 e5d3082bb245..00b9a46f340a 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.content.res.Configuration.ORIENTATION_LANDSCAPE 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.SeekBar 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 @@ import org.mockito.MockitoAnnotations @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 @@ class MediaViewControllerTest : SysuiTestCase() { @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 @@ class MediaViewControllerTest : SysuiTestCase() { @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 @@ class MediaViewControllerTest : SysuiTestCase() { @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 @@ class MediaViewControllerTest : SysuiTestCase() { @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 @@ class MediaViewControllerTest : SysuiTestCase() { @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 @@ class MediaViewControllerTest : SysuiTestCase() { @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 @@ class MediaViewControllerTest : SysuiTestCase() { @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 @@ class MediaViewControllerTest : SysuiTestCase() { @Test fun setIsScrubbing_scrubbingViewsShownAndPrevNextHiddenOnlyInExpanded() { - whenever(mediaFlags.isMediaControlsRefactorEnabled()).thenReturn(true) + whenever(mediaFlags.isSceneContainerEnabled()).thenReturn(true) mediaViewController.attachPlayer(viewHolder) mediaViewController.setUpNextButtonInfo(true) @@ -524,7 +524,7 @@ class MediaViewControllerTest : SysuiTestCase() { @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 0319aaaedd27..e87f17601c47 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.Color 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.Mockito.verify 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 120836959fdc..d073cf1ac9db 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 @@ import org.mockito.MockitoAnnotations @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 d3c703ccce46..cdb060cee645 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 e1c2d3f115ed..4da7b2ac3700 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.MediaMetadata 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.Mockito.`when` as whenever 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 86f3062bca35..bb9d20f88aa3 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.Test 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/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java index 95e34a9e54f3..ec02c6445b57 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java @@ -35,13 +35,13 @@ import static org.mockito.Mockito.when; import android.app.WallpaperColors; import android.graphics.Bitmap; import android.graphics.drawable.Icon; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; import android.widget.LinearLayout; import android.widget.SeekBar; import androidx.core.graphics.drawable.IconCompat; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.media.LocalMediaManager; @@ -62,7 +62,7 @@ import java.util.List; import java.util.stream.Collectors; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class MediaOutputAdapterTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java index 9616f6106a04..d5cd86ec0b76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java @@ -34,7 +34,6 @@ import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.Bundle; import android.os.PowerExemptionManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; import android.widget.Button; @@ -42,6 +41,7 @@ import android.widget.ImageView; import android.widget.TextView; import androidx.core.graphics.drawable.IconCompat; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager; @@ -67,7 +67,7 @@ import java.util.ArrayList; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class MediaOutputBaseDialogTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java index 16b00c0b18b4..87d224579e95 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java @@ -33,13 +33,13 @@ import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.media.AudioManager; import android.media.session.MediaSessionManager; import android.os.PowerExemptionManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.bluetooth.CachedBluetoothDevice; @@ -72,7 +72,7 @@ import java.util.ArrayList; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class MediaOutputBroadcastDialogTest extends SysuiTestCase { 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 45ae50623612..856bc0b78215 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.PowerExemptionManager; 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.ArrayList; 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 1e8fbeac05bd..50ae25ccfc00 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 static org.mockito.Mockito.verify; 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.Test; 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/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java index 92d0a72e300c..f20b04ae0e5c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java @@ -38,12 +38,12 @@ import android.os.UserHandle; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.FeatureFlagUtils; import android.view.View; import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.MediumTest; import com.android.internal.logging.UiEventLogger; @@ -75,7 +75,7 @@ import java.util.List; import java.util.function.Consumer; @MediumTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class MediaOutputDialogTest extends SysuiTestCase { 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 a82884377602..8e9a1f9d52f5 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 @@ package com.android.systemui.media.dream; 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.Mock; 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 8f8630e90694..a49819e5e7cc 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.never; 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.Mock; 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 27f59d292927..f1d833f7fa15 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.graphics.drawable.Drawable 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.Mockito.`when` as whenever 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 3be50b1baedf..111b8d4b1b30 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.media.MediaRoute2Info 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.WindowManager 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.Mockito.`when` as whenever 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 da448aa20289..62382570a2b1 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.Test 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 253607846e0f..22bdfe8f3cb3 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 @@ package com.android.systemui.mediaprojection.appselector 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.junit.runner.RunWith 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 db36131b825e..a73df0767dbc 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.ColorSpace 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 kotlinx.coroutines.test.runTest 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 fa1c8f8ea2c7..a0cd835b4ec1 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.ComponentName 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.Dispatchers 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 6ac86f58517d..2f61579d53c3 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 @@ package com.android.systemui.mediaprojection.appselector.data import android.app.ActivityManager.RecentTaskInfo import android.content.pm.UserInfo import android.os.UserManager -import android.testing.AndroidTestingRunner +import androidx.test.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.Test 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 e4877808f133..b7fefc0a202f 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 kotlinx.coroutines.test.runTest 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 c63efa1ba38a..ea5603d71e13 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.mock 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 @@ abstract class BaseScreenCaptureDevicePolicyResolverTest(private val preconditio } } -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) @SmallTest class IsAllowedScreenCaptureDevicePolicyResolverTest( private val test: IsScreenCaptureAllowedTestCase @@ -468,7 +469,7 @@ class IsAllowedScreenCaptureDevicePolicyResolverTest( } } -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) @SmallTest class IsCompletelyNotAllowedScreenCaptureDevicePolicyResolverTest( private val test: IsScreenCaptureCompletelyDisabledTestCase diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt index e044eeca8303..548366e3fc38 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt @@ -18,11 +18,11 @@ package com.android.systemui.mediaprojection.permission import android.app.AlertDialog import android.media.projection.MediaProjectionConfig -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.WindowManager import android.widget.Spinner 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.flags.FeatureFlagsClassic @@ -40,7 +40,7 @@ import org.junit.runner.RunWith import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() { 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 16c92ecc2181..8fe8878c6918 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.junit.runner.RunWith 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 bda0e1ed5c46..d3ce871994f6 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 kotlinx.coroutines.test.runTest 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 33e65f26716a..7fe55b85fcd8 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 kotlinx.coroutines.test.runTest 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 ad18099fefb9..7417dac5ceec 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 @@ package com.android.systemui.mediaprojection.taskswitcher.ui 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.never 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 a468953b971e..5bedc134a1ee 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 kotlinx.coroutines.test.runTest 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 c06a28e3d840..a3be9e35b912 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.android.systemui.testKosmos 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 f03f4f7375f5..9a78bd93f424 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.mock; 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 com.android.systemui.settings.FakeDisplayTracker; 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/NavigationBarButtonTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java index 3eb73290636f..85244fd5ce95 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarButtonTest.java @@ -27,12 +27,12 @@ import android.hardware.display.DisplayManager; import android.hardware.display.VirtualDisplay; import android.media.ImageReader; import android.os.SystemClock; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.util.Log; import android.view.Display; import android.view.DisplayInfo; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -53,7 +53,7 @@ import org.mockito.MockitoAnnotations; import java.util.function.Predicate; /** atest NavigationBarButtonTest */ -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class NavigationBarButtonTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java index 354a87a95fda..d5361acba308 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarControllerImplTest.java @@ -39,9 +39,9 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.util.SparseArray; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.StaticMockitoSession; @@ -72,7 +72,7 @@ import org.mockito.MockitoAnnotations; import java.util.Optional; /** atest NavigationBarControllerTest */ -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class NavigationBarControllerImplTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java index 52d02b631f89..a358c18ec609 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarInflaterViewTest.java @@ -22,12 +22,12 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.util.SparseArray; import android.view.View; import android.widget.FrameLayout; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -41,7 +41,7 @@ import org.junit.Test; import org.junit.runner.RunWith; /** atest NavigationBarInflaterViewTest */ -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class NavigationBarInflaterViewTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 6cea1e895e74..2b60f650e162 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -57,7 +57,6 @@ import android.os.Handler; import android.os.SystemClock; import android.provider.DeviceConfig; import android.telecom.TelecomManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.Display; @@ -73,6 +72,7 @@ import android.view.WindowMetrics; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.InputMethodManager; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -132,7 +132,7 @@ import org.mockito.MockitoAnnotations; import java.util.Optional; import java.util.concurrent.Executor; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) @SmallTest public class NavigationBarTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java index fb08bf5f1777..fbfd35fd5b9c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTransitionsTest.java @@ -25,9 +25,9 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -46,7 +46,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class NavigationBarTransitionsTest extends SysuiTestCase { @@ -105,4 +105,4 @@ public class NavigationBarTransitionsTest extends SysuiTestCase { assertTrue(mTransitions.isLightsOut(BarTransitions.MODE_LIGHTS_OUT)); } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java index a1010a01f1e4..841f620485ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java @@ -40,11 +40,11 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.hardware.input.InputManagerGlobal; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.view.KeyEvent; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -60,7 +60,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class KeyButtonViewTest extends SysuiTestCase { @@ -164,4 +164,4 @@ public class KeyButtonViewTest extends SysuiTestCase { verify(mUiEventLogger, times(1)).log(expected); } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java index 038b42be7fb2..6a3c6157a0a0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/NearestTouchFrameTest.java @@ -28,11 +28,11 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.MotionEvent; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -46,7 +46,7 @@ import org.mockito.MockitoAnnotations; import java.util.Map; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class NearestTouchFrameTest extends SysuiTestCase { @@ -276,4 +276,4 @@ public class NearestTouchFrameTest extends SysuiTestCase { v.setBottom(height); return v; } -}
\ No newline at end of file +} 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 f1c97dd45f09..b169cc12f08a 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 @@ -17,7 +17,6 @@ package com.android.systemui.navigationbar.gestural import android.os.Handler -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.HapticFeedbackConstants import android.view.MotionEvent @@ -26,6 +25,7 @@ import android.view.MotionEvent.ACTION_MOVE import android.view.MotionEvent.ACTION_UP import android.view.ViewConfiguration import android.view.WindowManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.jank.Cuj import com.android.internal.util.LatencyTracker @@ -49,12 +49,13 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class BackPanelControllerTest : SysuiTestCase() { 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/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt index 5f206b373610..f3cea3e8bb96 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/FloatingRotationButtonPositionCalculatorTest.kt @@ -8,10 +8,12 @@ import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalcul import com.android.systemui.shared.rotation.FloatingRotationButtonPositionCalculator.Position import com.google.common.truth.Truth.assertThat import org.junit.Test +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameter +import platform.test.runner.parameterized.Parameters import org.junit.runner.RunWith -import org.junit.runners.Parameterized -@RunWith(Parameterized::class) +@RunWith(ParameterizedAndroidJunit4::class) @SmallTest internal class FloatingRotationButtonPositionCalculatorTest( private val testCase: TestCase, @@ -61,7 +63,7 @@ internal class FloatingRotationButtonPositionCalculatorTest( MARGIN_DEFAULT, MARGIN_TASKBAR_LEFT, MARGIN_TASKBAR_BOTTOM, false ) - @Parameterized.Parameters(name = "{0}") + @Parameters(name = "{0}") @JvmStatic fun getParams(): Collection<TestCase> = listOf( diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt index bdb095a3a209..0f13f039e6ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/LaunchNotesRoleSettingsTrampolineActivityTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.notetask import android.content.Context import android.content.Intent -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule import com.android.dx.mockito.inline.extended.ExtendedMockito.verify @@ -37,7 +37,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper class LaunchNotesRoleSettingsTrampolineActivityTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt index 06127a7d83e7..9ef6b9c13315 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskBubblesServiceTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.notetask import android.content.Intent import android.graphics.drawable.Icon import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import androidx.test.runner.AndroidJUnit4 import com.android.systemui.res.R import com.android.systemui.SysuiTestCase import com.android.systemui.notetask.NoteTaskBubblesController.NoteTaskBubblesService diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index b7618d290f53..0196f95e29be 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -42,9 +42,9 @@ import android.graphics.drawable.Icon import android.os.UserHandle import android.os.UserManager import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.ext.truth.content.IntentSubject.assertThat import androidx.test.filters.SmallTest -import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase import com.android.systemui.notetask.NoteTaskController.Companion.EXTRA_SHORTCUT_BADGE_OVERRIDE_PACKAGE import com.android.systemui.notetask.NoteTaskController.Companion.SHORTCUT_ID diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt index 4101c94daa2f..c9a5d0620c65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskEventLoggerTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.notetask import android.os.UserHandle +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import androidx.test.runner.AndroidJUnit4 import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase import com.android.systemui.notetask.NoteTaskEventLogger.NoteTaskUiEvent.NOTE_CLOSED_VIA_STYLUS_TAIL_BUTTON diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt index 2c86a8dd47b1..0c88da750885 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoResolverTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.notetask import android.app.role.RoleManager import android.content.pm.ApplicationInfo import android.content.pm.PackageManager +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.util.mockito.eq import com.android.systemui.util.mockito.whenever diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt index 24f39d187f88..8f4078b88fc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInfoTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.notetask 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.notetask.NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT_IN_MULTI_WINDOW_MODE @@ -26,7 +26,7 @@ import org.junit.runner.RunWith /** atest SystemUITests:NoteTaskInfoTest */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) internal class NoteTaskInfoTest : SysuiTestCase() { @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt index 78330078076c..1ec4814181ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt @@ -19,12 +19,12 @@ import android.app.role.RoleManager import android.app.role.RoleManager.ROLE_NOTES import android.os.UserHandle import android.os.UserManager -import android.testing.AndroidTestingRunner import android.view.KeyEvent import android.view.KeyEvent.ACTION_DOWN import android.view.KeyEvent.ACTION_UP import android.view.KeyEvent.KEYCODE_N import android.view.KeyEvent.KEYCODE_STYLUS_BUTTON_TAIL +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.SysuiTestCase @@ -54,7 +54,7 @@ import org.mockito.MockitoAnnotations.initMocks /** atest SystemUITests:NoteTaskInitializerTest */ @OptIn(ExperimentalCoroutinesApi::class, InternalNoteTaskApi::class) @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) internal class NoteTaskInitializerTest : SysuiTestCase() { @Mock lateinit var commandQueue: CommandQueue diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt index 231b3331ce52..f624f2029b36 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt @@ -25,7 +25,7 @@ import android.content.pm.PackageManager.ApplicationInfoFlags import android.hardware.input.InputSettings import android.os.UserHandle import android.os.UserManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.systemui.SysuiTestCase @@ -63,7 +63,7 @@ import org.mockito.quality.Strictness /** atest SystemUITests:NoteTaskQuickAffordanceConfigTest */ @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) internal class NoteTaskQuickAffordanceConfigTest : SysuiTestCase() { @Mock lateinit var controller: NoteTaskController diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt index 1f0f0d70a858..9969dcd5bce2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/shortcut/LaunchNoteTaskActivityTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.notetask.shortcut -import android.testing.AndroidTestingRunner import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.rule.ActivityTestRule import com.android.dx.mockito.inline.extended.ExtendedMockito.verify @@ -35,7 +35,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper class LaunchNoteTaskActivityTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java index 1b713dd8de88..3673a25b68b2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/NotificationHelperTest.java @@ -36,8 +36,8 @@ import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.util.ArrayUtils; @@ -52,7 +52,7 @@ import org.junit.runner.RunWith; import java.util.List; import java.util.Set; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class NotificationHelperTest extends SysuiTestCase { private static final String SHORTCUT_ID_1 = "101"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java index 0d1749c51994..dae34524c203 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleBackupFollowUpJobTest.java @@ -35,8 +35,8 @@ import android.content.pm.PackageManager; import android.net.Uri; import android.os.RemoteException; import android.preference.PreferenceManager; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -56,7 +56,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class PeopleBackupFollowUpJobTest extends SysuiTestCase { private static final String SHORTCUT_ID_1 = "101"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java index 50ab1c73227e..776cf1992088 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleProviderTest.java @@ -29,9 +29,9 @@ import android.net.Uri; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; import android.widget.RemoteViews; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -46,7 +46,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class PeopleProviderTest extends SysuiTestCase { private static final String TAG = "PeopleProviderTest"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java index 84a8ab0a330b..48afaa021f71 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java @@ -52,9 +52,9 @@ import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; import android.provider.ContactsContract; -import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.appwidget.IAppWidgetService; @@ -81,7 +81,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class PeopleSpaceUtilsTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java index 3d1da00dda62..a90c10a047a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/PeopleTileViewHelperTest.java @@ -52,12 +52,12 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; import android.util.DisplayMetrics; import android.view.View; import android.widget.RemoteViews; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -73,7 +73,7 @@ import org.mockito.MockitoAnnotations; import java.time.Duration; import java.util.Arrays; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class PeopleTileViewHelperTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java index 7cd5e22a9b97..ae7fba939944 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/SharedPreferencesHelperTest.java @@ -24,8 +24,8 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.content.SharedPreferences; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -34,7 +34,7 @@ import com.android.systemui.people.widget.PeopleTileKey; import org.junit.Test; import org.junit.runner.RunWith; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class SharedPreferencesHelperTest extends SysuiTestCase { private static final String SHORTCUT_ID_1 = "101"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java index c8ebd1240149..e701dc63325f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java @@ -36,9 +36,9 @@ import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.service.notification.NotificationListenerService; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.statusbar.IStatusBarService; @@ -64,7 +64,7 @@ import org.mockito.MockitoAnnotations; import java.util.Optional; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class LaunchConversationActivityTest extends SysuiTestCase { private static final String EMPTY_STRING = ""; diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java index 5d526e102b91..b3ded15f582f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleBackupHelperTest.java @@ -36,8 +36,8 @@ import android.content.pm.PackageManager; import android.os.RemoteException; import android.os.UserHandle; import android.preference.PreferenceManager; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -57,7 +57,7 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class PeopleBackupHelperTest extends SysuiTestCase { private static final String SHORTCUT_ID_1 = "101"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java index 0998c0c3d32c..56a2adcef699 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java @@ -99,10 +99,10 @@ import android.platform.test.annotations.EnableFlags; import android.service.notification.ConversationChannelWrapper; import android.service.notification.StatusBarNotification; import android.service.notification.ZenModeConfig; -import android.testing.AndroidTestingRunner; import android.text.TextUtils; import androidx.preference.PreferenceManager; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -145,7 +145,7 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { private static final long MIN_LINGER_DURATION = 5; diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java index b95d3aaef32f..bdd8dc8875e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java @@ -42,9 +42,9 @@ import android.os.BatteryManager; import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; -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.UiEventLogger; @@ -70,7 +70,7 @@ import org.mockito.MockitoAnnotations; import java.lang.ref.WeakReference; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class PowerNotificationWarningsTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java index cae170f6f1c2..4f4f0d9f42cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerUITest.java @@ -38,11 +38,11 @@ import android.os.Temperature; import android.provider.Settings; import android.service.vr.IVrManager; import android.service.vr.IVrStateCallbacks; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableResources; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.settingslib.fuelgauge.Estimate; @@ -64,7 +64,7 @@ import org.mockito.MockitoAnnotations; import java.time.Duration; import java.util.concurrent.TimeUnit; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class PowerUITest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt index f3b114d662b5..02a3429f9111 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/power/data/repository/PowerRepositoryImplTest.kt @@ -21,6 +21,7 @@ import android.content.BroadcastReceiver import android.content.Intent import android.content.IntentFilter import android.os.PowerManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher @@ -37,7 +38,6 @@ import kotlinx.coroutines.runBlocking 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 @@ -48,7 +48,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class PowerRepositoryImplTest : SysuiTestCase() { private val systemClock = FakeSystemClock() diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt index 42cf9f4efc27..12c9eb900ee1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/power/domain/interactor/PowerInteractorTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.power.domain.interactor import android.os.PowerManager +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector @@ -39,13 +40,12 @@ import kotlinx.coroutines.runBlocking 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 @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class PowerInteractorTest : SysuiTestCase() { private lateinit var underTest: PowerInteractor diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt index 14ecf93a134e..4bee924225d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/AppOpsPrivacyItemMonitorTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.privacy import android.app.AppOpsManager import android.content.pm.UserInfo import android.os.UserHandle -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.appops.AppOpItem @@ -52,7 +52,7 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @RunWithLooper class AppOpsPrivacyItemMonitorTest : SysuiTestCase() { @@ -385,4 +385,4 @@ class AppOpsPrivacyItemMonitorTest : SysuiTestCase() { `when`(privacyConfig.locationAvailable).thenReturn(value) argCaptorConfigCallback.value.onFlagLocationChanged(value) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt index dcee5a716ceb..0d6652cca0e8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyChipBuilderTest.kt @@ -16,8 +16,8 @@ package com.android.systemui.privacy +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase import org.junit.Assert.assertEquals import org.junit.Test @@ -74,4 +74,4 @@ class PrivacyChipBuilderTest : SysuiTestCase() { val appList = textBuilder.appsAndTypes.map { it.first }.map { it.packageName } assertEquals(listOf("Camera", "Microphone", "Location"), appList) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt index 272f149f379b..4768b88afe22 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyConfigFlagsTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.privacy import android.provider.DeviceConfig -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.config.sysui.SystemUiDeviceConfigFlags import com.android.systemui.SysuiTestCase @@ -37,7 +37,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class PrivacyConfigFlagsTest : SysuiTestCase() { companion object { @@ -146,4 +146,4 @@ class PrivacyConfigFlagsTest : SysuiTestCase() { false ) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt index 84e9107f177c..58afcb76b70e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerTest.kt @@ -30,7 +30,7 @@ import android.os.Process.SYSTEM_UID import android.os.UserHandle import android.permission.PermissionGroupUsage import android.permission.PermissionManager -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase @@ -64,7 +64,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class PrivacyDialogControllerTest : SysuiTestCase() { companion object { @@ -824,4 +824,4 @@ class PrivacyDialogControllerTest : SysuiTestCase() { `when`(usage.proxyLabel).thenReturn(proxyLabel) return usage } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt index 0c7e09988a00..1c631616f9dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt @@ -30,8 +30,8 @@ import android.os.Process.SYSTEM_UID import android.os.UserHandle import android.permission.PermissionGroupUsage import android.permission.PermissionManager -import android.testing.AndroidTestingRunner import android.widget.LinearLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase @@ -66,7 +66,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class PrivacyDialogControllerV2Test : SysuiTestCase() { companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt index b7541456a76c..9ac04cf98375 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogTest.kt @@ -16,11 +16,11 @@ package com.android.systemui.privacy -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import android.view.ViewGroup import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -38,7 +38,7 @@ import android.content.Intent import android.text.TextUtils @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class PrivacyDialogTest : SysuiTestCase() { @@ -401,4 +401,4 @@ class PrivacyDialogTest : SysuiTestCase() { assertThat(TextUtils.isEmpty(dialog.window?.attributes?.title)).isFalse() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt index 6c01ba5b145b..f7cf4582566e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogV2Test.kt @@ -17,12 +17,12 @@ package com.android.systemui.privacy import android.content.Intent -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -38,7 +38,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class PrivacyDialogV2Test : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt index d56363231827..4f1fb71846ac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyItemControllerTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.privacy import android.app.ActivityManager import android.os.UserHandle -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.dump.DumpManager @@ -49,7 +49,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @RunWithLooper class PrivacyItemControllerTest : SysuiTestCase() { @@ -417,4 +417,4 @@ class PrivacyItemControllerTest : SysuiTestCase() { assertTrue(privacyItemController.privacyList.isEmpty()) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java index f573358e9746..5250d56edc0b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/process/condition/SystemProcessConditionTest.java @@ -19,9 +19,9 @@ package com.android.systemui.process.condition; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -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; @@ -39,7 +39,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class SystemProcessConditionTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java index e905e9cad459..8ccaf6bc0651 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qrcodescanner/controller/QRCodeScannerControllerTest.java @@ -34,9 +34,9 @@ import android.content.pm.ResolveInfo; import android.os.UserHandle; import android.provider.DeviceConfig; 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.internal.config.sysui.SystemUiDeviceConfigFlags; @@ -56,7 +56,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class QRCodeScannerControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java index dee1cc8b0354..1eeaef773689 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java @@ -30,9 +30,9 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.UserHandle; import android.provider.Settings.Secure; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -54,7 +54,7 @@ import org.mockito.MockitoAnnotations; import java.util.List; import java.util.concurrent.Executor; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class AutoAddTrackerTest extends SysuiTestCase { @@ -308,4 +308,4 @@ public class AutoAddTrackerTest extends SysuiTestCase { user ); } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java index d39a6352bf70..16ae4662c2d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java @@ -43,9 +43,9 @@ import android.os.Binder; import android.os.RemoteException; import android.os.UserHandle; import android.provider.DeviceConfig; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; @@ -73,7 +73,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class FgsManagerControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt index f98b68f2309b..3ae7a1672821 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/HeaderPrivacyIconsControllerTest.kt @@ -6,8 +6,8 @@ import android.content.Intent import android.content.IntentFilter import android.permission.PermissionManager import android.safetycenter.SafetyCenterManager -import android.testing.AndroidTestingRunner import android.view.View +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase @@ -46,7 +46,7 @@ private fun <T> eq(value: T): T = Mockito.eq(value) ?: value private fun <T> any(): T = Mockito.any<T>() @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class HeaderPrivacyIconsControllerTest : SysuiTestCase() { @Mock @@ -269,4 +269,4 @@ class HeaderPrivacyIconsControllerTest : SysuiTestCase() { whenever(privacyItemController.micCameraAvailable).thenReturn(micCamera) whenever(privacyItemController.locationAvailable).thenReturn(location) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt index 40eccad94340..466a09be4ff3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/LeftRightArrowPressedListenerTest.kt @@ -16,11 +16,11 @@ package com.android.systemui.qs -import android.testing.AndroidTestingRunner import android.view.KeyEvent import android.view.KeyEvent.KEYCODE_DPAD_LEFT import android.view.View import androidx.core.util.Consumer +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat @@ -28,7 +28,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class LeftRightArrowPressedListenerTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt index 8ef3f57103a0..db8612ad4182 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/PagedTileLayoutTest.kt @@ -1,9 +1,9 @@ package com.android.systemui.qs import android.content.Context -import android.testing.AndroidTestingRunner import android.view.View import android.widget.Scroller +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.qs.PageIndicator.PageScrollActionListener @@ -19,7 +19,7 @@ import org.mockito.Mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class PagedTileLayoutTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt index 8c5d99a84411..52ad931185e2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSContainerImplTest.kt @@ -1,9 +1,9 @@ package com.android.systemui.qs -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import android.widget.FrameLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -18,7 +18,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper @SmallTest class QSContainerImplTest : SysuiTestCase() { @@ -70,4 +70,4 @@ class QSContainerImplTest : SysuiTestCase() { eq(originalPadding) ) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java index 5ae0c24511b6..fb58b90d43f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFooterViewControllerTest.java @@ -27,11 +27,11 @@ import static org.mockito.Mockito.when; import android.content.ClipData; import android.content.ClipboardManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.plugins.ActivityStarter; @@ -49,7 +49,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class QSFooterViewControllerTest extends LeakCheckedTest { 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 6956beab418e..c0d390a99712 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java @@ -39,11 +39,9 @@ import static org.mockito.Mockito.verify; 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; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper.RunWithLooper; import android.view.Display; import android.view.LayoutInflater; @@ -51,19 +49,19 @@ import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; +import androidx.compose.ui.platform.ComposeView; import androidx.lifecycle.Lifecycle; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.keyguard.BouncerPanelExpansionCalculator; 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; @@ -84,7 +82,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) @SmallTest public class QSImplTest extends SysuiTestCase { @@ -112,9 +110,7 @@ public class QSImplTest extends SysuiTestCase { @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 @@ public class QSImplTest extends SysuiTestCase { } @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 @@ public class QSImplTest extends SysuiTestCase { @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 @@ public class QSImplTest extends SysuiTestCase { mock(QSLogger.class), mock(FooterActionsController.class), mFooterActionsViewModelFactory, - mFooterActionsViewBinder, - mLargeScreenShadeInterpolator, - mFeatureFlags + mLargeScreenShadeInterpolator ); } @@ -589,41 +611,20 @@ public class QSImplTest extends SysuiTestCase { 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/QSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt index 545d19dd771e..02c5b5ad214c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelControllerTest.kt @@ -1,8 +1,8 @@ package com.android.systemui.qs import android.content.res.Configuration +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner import android.testing.TestableResources import android.view.ContextThemeWrapper import com.android.internal.logging.MetricsLogger @@ -41,7 +41,7 @@ import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class QSPanelControllerTest : SysuiTestCase() { @Mock private lateinit var qsPanel: QSPanel diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt index 56f2905e834f..56e25fcd580c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelSwitchToParentTest.kt @@ -18,9 +18,9 @@ package com.android.systemui.qs import com.google.common.truth.Truth.assertThat +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner import android.view.View import android.view.ViewGroup import android.widget.FrameLayout @@ -30,7 +30,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class QSPanelSwitchToParentTest : SysuiTestCase() { @@ -159,4 +159,4 @@ class QSPanelSwitchToParentTest : SysuiTestCase() { private val ViewGroup.childrenList: List<View> get() = children.toList() -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt index e2a4d6727e80..2d282dcd1514 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSPanelTest.kt @@ -14,7 +14,6 @@ package com.android.systemui.qs import android.graphics.Rect -import android.testing.AndroidTestingRunner import android.testing.TestableContext import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper @@ -26,6 +25,7 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.accessibility.AccessibilityNodeInfo import android.widget.FrameLayout import android.widget.LinearLayout +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -45,7 +45,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper @SmallTest class QSPanelTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java index 0abcc64e0dc8..dad65f5add42 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java @@ -42,7 +42,6 @@ import android.os.Handler; import android.os.Looper; import android.provider.DeviceConfig; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.text.SpannableStringBuilder; @@ -51,6 +50,7 @@ import android.view.View; import android.widget.TextView; import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -83,7 +83,7 @@ import org.mockito.MockitoAnnotations; */ @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper public class QSSecurityFooterTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt index e2a0626d9849..ecdabbf9853e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSquishinessControllerTest.kt @@ -1,6 +1,6 @@ package com.android.systemui.qs -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Before @@ -12,7 +12,7 @@ import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class QSSquishinessControllerTest : SysuiTestCase() { @@ -45,4 +45,4 @@ class QSSquishinessControllerTest : SysuiTestCase() { verify(qsPanelController).setSquishinessFraction(0.5f) verify(quickQsPanelController).setSquishinessFraction(0.5f) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 5e14b1a60ddb..6d1bc824c3c1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -41,10 +41,10 @@ import android.database.ContentObserver; import android.os.Handler; import android.os.Looper; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; import android.util.SparseArray; import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -53,7 +53,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.Expandable; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.nano.SystemUIProtoDump; -import com.android.systemui.flags.FakeFeatureFlags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.qs.QSFactory; @@ -95,7 +94,7 @@ import java.util.concurrent.Executor; import javax.inject.Provider; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class QSTileHostTest extends SysuiTestCase { @@ -133,8 +132,6 @@ public class QSTileHostTest extends SysuiTestCase { private SparseArray<SharedPreferences> mSharedPreferencesByUser; - private FakeFeatureFlags mFeatureFlags; - private QSPipelineFlagsRepository mQSPipelineFlagsRepository; private FakeExecutor mMainExecutor; @@ -144,7 +141,6 @@ public class QSTileHostTest extends SysuiTestCase { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mFeatureFlags = new FakeFeatureFlags(); mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE); mSetFlagsRule.disableFlags(FLAG_QS_NEW_TILES); @@ -170,12 +166,12 @@ public class QSTileHostTest extends SysuiTestCase { saveSetting(""); setUpTileFactory(); mQSTileHost = new TestQSTileHost(mContext, () -> null, mDefaultFactory, mMainExecutor, - mPluginManager, mTunerService, () -> mAutoTiles, mShadeController, + mPluginManager, mTunerService, () -> mAutoTiles, () -> mShadeController, mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister, mTileLifecycleManagerFactory, mUserFileManager, mQSPipelineFlagsRepository); mMainExecutor.runAllReady(); - mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) { + mSecureSettings.registerContentObserverForUserSync(SETTING, new ContentObserver(null) { @Override public void onChange(boolean selfChange) { super.onChange(selfChange); @@ -689,7 +685,7 @@ public class QSTileHostTest extends SysuiTestCase { QSFactory defaultFactory, Executor mainExecutor, PluginManager pluginManager, TunerService tunerService, Provider<AutoTileManager> autoTiles, - ShadeController shadeController, QSLogger qsLogger, + Lazy<ShadeController> shadeController, QSLogger qsLogger, UserTracker userTracker, SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister, TileLifecycleManager.Factory tileLifecycleManagerFactory, diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt index fee4b534d8dd..369bb228494c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelControllerTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.qs import android.content.res.Configuration -import android.testing.AndroidTestingRunner import android.view.ContextThemeWrapper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.logging.testing.UiEventLoggerFake @@ -50,7 +50,7 @@ import javax.inject.Provider import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class QuickQSPanelControllerTest : SysuiTestCase() { @Mock private lateinit var quickQSPanel: QuickQSPanel diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt index e5369fcae0b9..3d6ba94556b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickQSPanelTest.kt @@ -1,10 +1,10 @@ package com.android.systemui.qs -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.ViewGroup import android.view.accessibility.AccessibilityNodeInfo 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.qs.logging.QSLogger @@ -16,7 +16,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper @SmallTest class QuickQSPanelTest : SysuiTestCase() { @@ -63,4 +63,4 @@ class QuickQSPanelTest : SysuiTestCase() { quickQSPanel.performAccessibilityAction(AccessibilityNodeInfo.ACTION_EXPAND, null) Mockito.verify(mockRunnable).run() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt index 4915e555dcc5..a0ccec11fec3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QuickStatusBarHeaderControllerTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.qs import android.content.Context -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.After @@ -31,7 +31,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class QuickStatusBarHeaderControllerTest : SysuiTestCase() { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt index bc947fb910e5..be388a17ab5d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/TileStateToProtoTest.kt @@ -2,8 +2,8 @@ package com.android.systemui.qs import android.content.ComponentName import android.service.quicksettings.Tile -import android.testing.AndroidTestingRunner import android.widget.Switch +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.qs.QSTile @@ -12,7 +12,7 @@ import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class TileStateToProtoTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt index 8f06fe2e3050..90e0dd80c55c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/UserSettingObserverTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.qs import android.os.Handler -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.settings.FakeSettings @@ -33,7 +33,7 @@ import org.junit.runner.RunWith private typealias Callback = (Int, Boolean) -> Unit @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class UserSettingObserverTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java index 6e2f5db2eeda..9f2b1ea9e37e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java @@ -24,11 +24,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.os.Bundle; -import android.testing.AndroidTestingRunner; import android.view.View; import android.view.accessibility.AccessibilityNodeInfo; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -41,7 +41,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class TileAdapterDelegateTest extends SysuiTestCase { private static final int MOVE_TO_POSITION_ID = R.id.accessibility_action_qs_move_to_position; diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java index f8a98afef978..cbcd8104ce35 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java @@ -17,10 +17,10 @@ package com.android.systemui.qs.customize; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.testing.UiEventLoggerFake; @@ -37,7 +37,7 @@ import org.mockito.MockitoAnnotations; import java.util.Collections; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper @SmallTest public class TileAdapterTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java index 8bf743884359..09a6c2c7f1f7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileQueryHelperTest.java @@ -40,12 +40,12 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.provider.Settings; import android.service.quicksettings.Tile; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.text.TextUtils; import android.util.ArraySet; import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceId; @@ -75,7 +75,7 @@ import java.util.Set; import java.util.concurrent.Executor; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class TileQueryHelperTest extends SysuiTestCase { private static final String CURRENT_TILES = "internet,dnd,nfc"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt index 81d02b8043b9..14eaa0358ed0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileStatePersisterTest.kt @@ -20,7 +20,7 @@ import android.content.ComponentName import android.content.Context import android.content.SharedPreferences import android.service.quicksettings.Tile -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.util.mockito.capture @@ -41,7 +41,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class CustomTileStatePersisterTest : SysuiTestCase() { companion object { @@ -167,4 +167,4 @@ class CustomTileStatePersisterTest : SysuiTestCase() { assertThat(customTileStatePersister.readState(KEY)!!.label).isNull() } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt index a8e9db5d527c..bd03acb6e7ec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/CustomTileTest.kt @@ -31,8 +31,8 @@ import android.os.Handler import android.os.Parcel import android.service.quicksettings.IQSTileService import android.service.quicksettings.Tile +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.IWindowManager import com.android.internal.logging.MetricsLogger @@ -74,7 +74,7 @@ import java.util.Arrays @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class CustomTileTest : SysuiTestCase() { @@ -563,4 +563,4 @@ private fun copyTileUsingParcel(t: Tile): Tile { parcel.setDataPosition(0) return Tile.CREATOR.createFromParcel(parcel) -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt index 78c2acf7209e..2db5e83cf185 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt @@ -22,11 +22,11 @@ import android.graphics.Canvas import android.graphics.Color import android.graphics.drawable.Drawable import android.graphics.drawable.Icon -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -48,7 +48,7 @@ import org.mockito.MockitoAnnotations import java.util.Arrays @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class TileRequestDialogTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt index 3afa6adbc972..89ec687ad123 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServiceRequestControllerTest.kt @@ -22,7 +22,7 @@ import android.content.ComponentName import android.content.DialogInterface import android.graphics.drawable.Icon import android.os.RemoteException -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.InstanceId import com.android.internal.statusbar.IAddTileResultCallback @@ -51,7 +51,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class TileServiceRequestControllerTest : SysuiTestCase() { companion object { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt index 720c25a3f719..c5a2370adcda 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/domain/interactor/FooterActionsInteractorTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.qs.footer.domain.interactor import android.content.Context import android.content.Intent 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.internal.logging.nano.MetricsProto import com.android.internal.logging.testing.FakeMetricsLogger @@ -49,7 +49,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class FooterActionsInteractorTest : SysuiTestCase() { private lateinit var utils: FooterActionsTestUtils diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt index 1cb3bf6c7100..31652a58b217 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt @@ -18,10 +18,10 @@ package com.android.systemui.qs.footer.ui.viewmodel import android.graphics.drawable.Drawable import android.os.UserManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper import android.view.ContextThemeWrapper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.settingslib.Utils import com.android.settingslib.drawable.UserIconDrawable @@ -57,7 +57,7 @@ import org.mockito.Mockito.anyInt import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper class FooterActionsViewModelTest : SysuiTestCase() { private val testScope = TestScope() 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 2da4b7296c35..b206f56f95a6 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 @@ -16,7 +16,7 @@ package com.android.systemui.qs.panels.domain.interactor -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 @@ -31,9 +31,6 @@ import com.android.systemui.qs.pipeline.shared.TileSpec 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 @@ -42,26 +39,25 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) 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 bda48adbfcc3..1e2e82ffeca5 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 @@ -16,7 +16,7 @@ package com.android.systemui.qs.panels.domain.interactor -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 @@ -25,33 +25,29 @@ import com.android.systemui.qs.panels.data.repository.iconTilesRepository 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 @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) 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/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt index 5201e5d9ccf7..12c566ca9e2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.qs.tileimpl import android.content.ComponentName -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.qs.QSHost @@ -93,7 +93,7 @@ private val specMap = mapOf( "font_scaling" to FontScalingTile::class.java ) -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class QSFactoryImplTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java index 81a960419555..2580ac2c8da7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java @@ -27,10 +27,10 @@ import static org.mockito.Mockito.when; import android.graphics.drawable.AnimatedVectorDrawable; import android.graphics.drawable.Drawable; import android.service.quicksettings.Tile; -import android.testing.AndroidTestingRunner; import android.testing.UiThreadTest; import android.widget.ImageView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -43,7 +43,7 @@ import org.junit.runner.RunWith; import org.mockito.InOrder; import org.mockito.Mockito; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @UiThreadTest @SmallTest public class QSIconViewImplTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java index c70624411c37..e46416c1b321 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSTileImplTest.java @@ -48,11 +48,11 @@ import android.os.Handler; import android.os.Looper; import android.provider.Settings; import android.service.quicksettings.Tile; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.annotation.Nullable; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.InstanceId; @@ -85,7 +85,7 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) @SmallTest public class QSTileImplTest extends SysuiTestCase { 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 db11c3e89160..661830848682 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,20 +19,24 @@ package com.android.systemui.qs.tileimpl 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 import android.text.TextUtils import android.view.ContextThemeWrapper import android.view.View import android.view.accessibility.AccessibilityNodeInfo import android.widget.TextView +import androidx.test.ext.junit.runners.AndroidJUnit4 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 @@ -41,7 +45,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest @TestableLooper.RunWithLooper(setAsMainLooper = true) class QSTileViewImplTest : SysuiTestCase() { @@ -536,10 +540,30 @@ class QSTileViewImplTest : SysuiTestCase() { 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 @@ class QSTileViewImplTest : SysuiTestCase() { ) { 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/qs/tileimpl/ResourceIconTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt index 9c1cad62d1aa..e112bb05ab2e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/ResourceIconTest.kt @@ -16,14 +16,14 @@ package com.android.systemui.qs.tileimpl -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import org.junit.Assert.assertEquals import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class ResourceIconTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt index f2400ec85feb..9c20a6c771e7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/TilesStatesTextTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.qs.tileimpl -import android.testing.AndroidTestingRunner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.MediumTest import com.android.systemui.res.R import com.android.systemui.SysuiTestCase @@ -26,7 +26,7 @@ import org.junit.Assert.assertNotEquals import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @MediumTest class TilesStatesTextTest : SysuiTestCase() { @@ -76,4 +76,4 @@ class TilesStatesTextTest : SysuiTestCase() { assertThat(SubtitleArrayMapping.getSubtitleId(null)) .isEqualTo(R.array.tile_states_default) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt index ad87315c34f7..7a99aefc98fe 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.qs.tiles import android.net.ConnectivityManager import android.os.Handler -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.MetricsLogger import com.android.internal.telephony.flags.Flags @@ -50,7 +50,7 @@ import org.mockito.kotlin.any import org.mockito.kotlin.times import org.mockito.kotlin.verify -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class AirplaneModeTileTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt index 52b84559f396..518b6de051bd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AlarmTileTest.kt @@ -5,8 +5,8 @@ import android.app.PendingIntent import android.os.Handler import android.provider.AlarmClock import android.service.quicksettings.Tile -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.MetricsLogger import com.android.systemui.SysuiTestCase @@ -33,7 +33,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class AlarmTileTest : SysuiTestCase() { @@ -152,4 +152,4 @@ class AlarmTileTest : SysuiTestCase() { verify(activityStarter).postStartActivityDismissingKeyguard(pendingIntent, null /* animationController */) } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt index 2c49e925edd6..d6be31450fc0 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt @@ -18,9 +18,9 @@ package com.android.systemui.qs.tiles import android.content.Context import android.os.Handler -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase @@ -50,7 +50,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest class BatterySaverTileTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt index 1ffbb7be49fc..9a924ed5a630 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt @@ -4,9 +4,9 @@ import android.bluetooth.BluetoothDevice import android.os.Handler import android.os.Looper import android.os.UserManager -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.internal.telephony.flags.Flags @@ -42,7 +42,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper(setAsMainLooper = true) @SmallTest class BluetoothTileTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt index 0e4b113f57ca..093cdf22a64b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.qs.tiles import android.os.Handler import android.provider.Settings import android.safetycenter.SafetyCenterManager -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.MetricsLogger import com.android.systemui.res.R @@ -44,7 +44,7 @@ import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class CameraToggleTileTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java index 46ee5692830e..50cf5cc5672e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CastTileTest.java @@ -32,10 +32,10 @@ import android.media.MediaRouter.RouteInfo; import android.media.projection.MediaProjectionInfo; import android.os.Handler; import android.service.quicksettings.Tile; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import androidx.lifecycle.LifecycleOwner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -73,7 +73,7 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.List; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class CastTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java index 2250ef33f9b4..028beb599644 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorCorrectionTileTest.java @@ -26,9 +26,9 @@ import static org.mockito.Mockito.when; import android.content.Intent; import android.os.Handler; 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.internal.logging.MetricsLogger; @@ -51,7 +51,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class ColorCorrectionTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java index ea43326a6c11..1343527e631b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ColorInversionTileTest.java @@ -26,9 +26,9 @@ import static org.mockito.Mockito.when; import android.content.Intent; import android.os.Handler; 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.internal.logging.MetricsLogger; @@ -54,7 +54,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class ColorInversionTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt index 043ddf5b433a..73ae4ee5aa0d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.qs.tiles import android.os.Handler -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.MetricsLogger import com.android.systemui.SysuiTestCase @@ -43,7 +43,7 @@ import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class DataSaverTileTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt index 874368bd2bd1..418d126ce3cf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DeviceControlsTileTest.kt @@ -22,9 +22,9 @@ import android.content.Intent import android.os.Handler import android.provider.Settings import android.service.quicksettings.Tile -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.lifecycle.LifecycleOwner +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.res.R @@ -70,7 +70,7 @@ import org.mockito.MockitoAnnotations import java.util.Optional @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class DeviceControlsTileTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt index 1173fa31fbb4..e01744e3576c 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt @@ -23,9 +23,9 @@ import android.os.Handler import android.provider.Settings import android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS import android.provider.Settings.Global.ZEN_MODE_OFF -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.ContextThemeWrapper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger import com.android.systemui.res.R @@ -59,7 +59,7 @@ import java.io.File import org.mockito.Mockito.`when` as whenever @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class DndTileTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java index a3c29757f1fc..190d80f9f6c4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java @@ -35,9 +35,9 @@ import android.os.RemoteException; import android.provider.Settings; import android.service.dreams.IDreamManager; import android.service.quicksettings.Tile; -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.MetricsLogger; @@ -63,7 +63,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class DreamTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt index c1a09288d8a2..f90463e7f589 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FlashlightTileTest.kt @@ -2,8 +2,8 @@ package com.android.systemui.qs.tiles import android.content.Context import android.os.Handler -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.MetricsLogger import com.android.systemui.res.R @@ -26,7 +26,7 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class FlashlightTileTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt index 1c42dd15ce3b..c854920cbf1f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.qs.tiles import android.content.Intent import android.os.Handler 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.internal.logging.MetricsLogger import com.android.systemui.SysuiTestCase @@ -52,7 +52,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class FontScalingTileTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java index 56671bf357ba..59ee0b843043 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HearingDevicesTileTest.java @@ -28,10 +28,10 @@ import android.os.Handler; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -57,7 +57,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; /** Tests for {@link HearingDevicesTile}. */ -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class HearingDevicesTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java index a85b49b66d5f..5bd6944e863f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/HotspotTileTest.java @@ -23,9 +23,9 @@ import static org.mockito.Mockito.when; import android.os.Handler; import android.service.quicksettings.Tile; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.ExtendedMockito; @@ -54,7 +54,7 @@ import org.mockito.MockitoSession; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class HotspotTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java index 0ea61f9548f6..8ea79d7cc230 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/InternetTileTest.java @@ -23,9 +23,9 @@ import static org.mockito.Mockito.when; import android.os.Handler; import android.service.quicksettings.Tile; -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.MetricsLogger; @@ -51,7 +51,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class InternetTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt index 62a50e369b7d..0a1455fe12cc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.qs.tiles import android.content.Context import android.os.Handler -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.MetricsLogger import com.android.systemui.res.R @@ -47,7 +47,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class LocationTileTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt index b98a7570bb6c..dbdf3a499f8b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.qs.tiles import android.os.Handler import android.provider.Settings import android.safetycenter.SafetyCenterManager -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.MetricsLogger import com.android.systemui.res.R @@ -44,7 +44,7 @@ import org.mockito.Mock import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class MicrophoneToggleTileTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java index f6bc69290e88..442a94887157 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NfcTileTest.java @@ -24,9 +24,9 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.PackageManager; import android.os.Handler; -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.MetricsLogger; @@ -47,7 +47,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class NfcTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt index a1d9e4149b95..f1c589512895 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/NightDisplayTileTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.qs.tiles import android.hardware.display.ColorDisplayManager import android.hardware.display.NightDisplayListener import android.os.Handler -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.MetricsLogger import com.android.systemui.res.R @@ -45,7 +45,7 @@ import org.mockito.Mockito.anyInt import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class NightDisplayTileTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java index c391153fecc1..d6fa124f3f91 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/OneHandedModeTileTest.java @@ -22,9 +22,9 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.os.Handler; -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.MetricsLogger; @@ -45,7 +45,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class OneHandedModeTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java index d7beb5dceec2..f8f82f2c2ed8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QRCodeScannerTileTest.java @@ -24,9 +24,9 @@ import static org.mockito.Mockito.when; import android.os.Handler; import android.service.quicksettings.Tile; -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.MetricsLogger; @@ -49,7 +49,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class QRCodeScannerTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java index 122d9e414d13..914bd0e13dc6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -52,9 +52,9 @@ import android.service.quickaccesswallet.QuickAccessWalletClient; import android.service.quickaccesswallet.QuickAccessWalletService; import android.service.quickaccesswallet.WalletCard; import android.service.quicksettings.Tile; -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.MetricsLogger; @@ -83,7 +83,7 @@ import org.mockito.MockitoAnnotations; import java.util.Collections; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class QuickAccessWalletTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt index 37654d515a21..df59e572bd3b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt @@ -18,8 +18,8 @@ package com.android.systemui.qs.tiles import android.os.Handler import android.service.quicksettings.Tile -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.MetricsLogger import com.android.systemui.SysuiTestCase @@ -33,6 +33,7 @@ import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor import com.android.systemui.recordissue.IssueRecordingState import com.android.systemui.recordissue.RecordIssueDialogDelegate +import com.android.systemui.recordissue.TraceurMessageSender import com.android.systemui.res.R import com.android.systemui.settings.UserContextProvider import com.android.systemui.statusbar.phone.KeyguardDismissUtil @@ -41,6 +42,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import java.util.concurrent.Executors import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -54,7 +56,7 @@ import org.mockito.MockitoAnnotations * This class tests the functionality of the RecordIssueTile. The initial state of the tile is * always be inactive at the start of these tests. */ -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class RecordIssueTileTest : SysuiTestCase() { @@ -70,6 +72,7 @@ class RecordIssueTileTest : SysuiTestCase() { @Mock private lateinit var dialogLauncherAnimator: DialogTransitionAnimator @Mock private lateinit var panelInteractor: PanelInteractor @Mock private lateinit var userContextProvider: UserContextProvider + @Mock private lateinit var traceurMessageSender: TraceurMessageSender @Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory @Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate @Mock private lateinit var dialog: SystemUIDialog @@ -102,6 +105,8 @@ class RecordIssueTileTest : SysuiTestCase() { dialogLauncherAnimator, panelInteractor, userContextProvider, + traceurMessageSender, + Executors.newSingleThreadExecutor(), issueRecordingState, delegateFactory, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java index 8eaa87680638..798e9fb208b7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ReduceBrightColorsTileTest.java @@ -25,9 +25,9 @@ import static org.mockito.Mockito.when; import android.os.Handler; import android.service.quicksettings.Tile; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.R; @@ -52,7 +52,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class ReduceBrightColorsTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java index c02fca7f264b..41930636cfa3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java @@ -27,10 +27,10 @@ import android.Manifest; import android.content.pm.PackageManager; import android.hardware.SensorPrivacyManager; import android.os.Handler; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableResources; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.MetricsLogger; @@ -58,7 +58,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class RotationLockTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index 507fb868f5a4..0d12483bad0a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java @@ -31,9 +31,9 @@ import static org.mockito.Mockito.when; import android.app.Dialog; import android.os.Handler; import android.service.quicksettings.Tile; -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.MetricsLogger; @@ -64,7 +64,7 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class ScreenRecordTileTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt index 47fc3ec51078..8324a7303cff 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UiModeNightTileTest.kt @@ -21,8 +21,8 @@ import android.content.Context import android.content.res.Configuration import android.content.res.Resources import android.os.Handler -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.MetricsLogger import com.android.systemui.res.R @@ -47,7 +47,7 @@ import org.mockito.Mock import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest class UiModeNightTileTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt index f9d69c2cce64..c764c548ed92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/UserDetailViewAdapterTest.kt @@ -19,10 +19,10 @@ package com.android.systemui.qs.tiles import android.content.Context import android.content.pm.UserInfo import android.graphics.Bitmap -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.internal.logging.testing.UiEventLoggerFake import com.android.internal.util.UserIcons @@ -45,7 +45,7 @@ import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class UserDetailViewAdapterTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java index ff712ad09679..b88861756889 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetAdapterTest.java @@ -13,11 +13,11 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.graphics.drawable.Drawable; -import android.testing.AndroidTestingRunner; import android.testing.TestableResources; import android.view.View; import android.widget.LinearLayout; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -39,7 +39,7 @@ import java.util.Arrays; import java.util.List; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class InternetAdapterTest extends SysuiTestCase { private static final String WIFI_KEY = "Wi-Fi_Key"; diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java index 29487cdace2d..5273495bec2f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateControllerTest.java @@ -51,7 +51,6 @@ import android.telephony.SubscriptionManager; import android.telephony.TelephonyCallback; import android.telephony.TelephonyDisplayInfo; import android.telephony.TelephonyManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableResources; import android.text.TextUtils; @@ -59,6 +58,7 @@ import android.view.Gravity; import android.view.View; import android.view.WindowManager; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; @@ -101,7 +101,7 @@ import java.util.Locale; import java.util.Map; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class InternetDialogDelegateControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java index aefcc87e79cc..ff8c4481adea 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java @@ -18,7 +18,6 @@ import android.content.DialogInterface; import android.content.Intent; import android.os.Handler; import android.telephony.TelephonyManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; import android.widget.LinearLayout; @@ -26,6 +25,7 @@ import android.widget.Switch; import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.dx.mockito.inline.extended.ExtendedMockito; @@ -56,7 +56,7 @@ import java.util.List; @Ignore("b/257089187") @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class InternetDialogDelegateTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java index 5d7ba7bc673d..57484c21c767 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/WifiStateWorkerTest.java @@ -35,8 +35,8 @@ import static org.mockito.Mockito.when; import android.content.Intent; import android.net.wifi.WifiManager; -import android.testing.AndroidTestingRunner; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -52,7 +52,7 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest public class WifiStateWorkerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt index e48d96b3763f..ad6c64b32ddc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.qs.user import android.content.DialogInterface import android.content.Intent import android.provider.Settings -import android.testing.AndroidTestingRunner import android.widget.Button +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase @@ -53,7 +53,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class UserSwitchDialogControllerTest : SysuiTestCase() { @Mock diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java index b75b3188ae64..3aaaf95810a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java @@ -29,12 +29,12 @@ import android.content.res.Configuration; import android.content.res.Resources; import android.hardware.devicestate.DeviceState; import android.hardware.devicestate.DeviceStateManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -54,7 +54,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class RearDisplayDialogControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt index 74deae323b5c..fc74586e7643 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recents/OverviewProxyServiceTest.kt @@ -23,9 +23,9 @@ import android.content.pm.ResolveInfo import android.os.PowerManager import android.os.Process import android.os.UserHandle -import android.testing.AndroidTestingRunner import android.testing.TestableContext import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.app.AssistUtils @@ -81,7 +81,7 @@ import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class OverviewProxyServiceTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt index fcc6b4f093ad..ca606505af68 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt @@ -20,10 +20,10 @@ import android.app.Dialog import android.content.Context import android.content.SharedPreferences import android.os.UserHandle -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.widget.Button import android.widget.Switch +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator @@ -63,7 +63,7 @@ import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class RecordIssueDialogDelegateTest : SysuiTestCase() { @@ -81,6 +81,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { @Mock private lateinit var sysuiState: SysUiState @Mock private lateinit var systemUIDialogManager: SystemUIDialogManager @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher + @Mock private lateinit var traceurMessageSender: TraceurMessageSender private val systemClock = FakeSystemClock() private val bgExecutor = FakeExecutor(systemClock) private val mainExecutor = FakeExecutor(systemClock) @@ -131,6 +132,7 @@ class RecordIssueDialogDelegateTest : SysuiTestCase() { userFileManager, screenCaptureDisabledDialogDelegate, issueRecordingState, + traceurMessageSender ) { latch.countDown() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt index fe80f702a3ae..ba7a65dd8e65 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/retail/data/repository/RetailModeSettingsRepositoryTest.kt @@ -17,7 +17,7 @@ package com.android.systemui.retail.data.repository import android.provider.Settings -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 @@ -33,7 +33,7 @@ import org.junit.runner.RunWith @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class RetailModeSettingsRepositoryTest : SysuiTestCase() { private val globalSettings = FakeGlobalSettings() diff --git a/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt index 8f131696f815..b53652067755 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/retail/domain/interactor/RetailModeInteractorImplTest.kt @@ -16,7 +16,7 @@ package com.android.systemui.retail.domain.interactor -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.retail.data.repository.FakeRetailModeRepository @@ -25,7 +25,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) class RetailModeInteractorImplTest : SysuiTestCase() { private val retailModeRepository = FakeRetailModeRepository() 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 bde1445acfa8..b8267a0e83d4 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 @@ package com.android.systemui.shade 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 androidx.test.filters.SmallTest 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.kosmos.testScope 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.Assert.assertThrows 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 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ambientTouchComponentFactory, communalContent, kosmos.sceneDataSourceDelegator, + kosmos.notificationStackScrollLayoutController ) } testableLooper = TestableLooper.get(this) @@ -166,6 +173,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ambientTouchComponentFactory, communalContent, kosmos.sceneDataSourceDelegator, + kosmos.notificationStackScrollLayoutController ) // First call succeeds. @@ -176,6 +184,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalClosed_doesNotIntercept() = with(kosmos) { @@ -187,6 +196,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_openGesture_interceptsTouches() = with(kosmos) { @@ -204,6 +214,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalTransitioning_interceptsTouches() = with(kosmos) { @@ -230,6 +241,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalOpen_interceptsTouches() = with(kosmos) { @@ -244,6 +256,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalAndBouncerShowing_doesNotIntercept() = with(kosmos) { @@ -262,6 +275,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_communalAndShadeShowing_doesNotIntercept() = with(kosmos) { @@ -278,6 +292,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @DisableFlags(FLAG_GLANCEABLE_HUB_FULLSCREEN_SWIPE) @Test fun onTouchEvent_containerViewDisposed_doesNotIntercept() = with(kosmos) { @@ -310,6 +325,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { ambientTouchComponentFactory, communalContent, kosmos.sceneDataSourceDelegator, + kosmos.notificationStackScrollLayoutController, ) assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED) @@ -329,6 +345,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { 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 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { } } + @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 041adea8decc..c3cedf84a864 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 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { 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 845744a54791..85541aa8abda 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 @@ public class QuickSettingsControllerImplBaseTest extends SysuiTestCase { 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 5363c57c1bad..308b3708e407 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 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { 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 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { 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 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { 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 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { 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 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { 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 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { 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 @@ public class ShadeCarrierGroupControllerTest extends LeakCheckedTest { 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 63ce2330698a..068e166de5bb 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.Handler 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.Optional 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 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() { } @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/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java index 80127682be02..dc9c22f566bf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java @@ -320,7 +320,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase { .thenReturn(1); ArgumentCaptor<ContentObserver> contentObserverCaptor = ArgumentCaptor.forClass( ContentObserver.class); - verify(mSecureSettings).registerContentObserverForUser(eq(SHOW_NOTIFICATION_SNOOZE), + verify(mSecureSettings).registerContentObserverForUserSync(eq(SHOW_NOTIFICATION_SNOOZE), contentObserverCaptor.capture(), anyInt()); ContentObserver contentObserver = contentObserverCaptor.getValue(); contentObserver.onChange(false); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt index 7943872558c3..2f77b33c96cb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt @@ -69,22 +69,21 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() { private val groupMembershipManager: GroupMembershipManager = mock() private val section = NotifSection(mock(), 0) - private val entry = NotificationEntryBuilder() - .setSection(section) - .setParent(GroupEntry.ROOT_ENTRY) - .build() + private val entry = + NotificationEntryBuilder().setSection(section).setParent(GroupEntry.ROOT_ENTRY).build() private lateinit var contentObserver: ContentObserver - private val adjustmentProvider = NotifUiAdjustmentProvider( - handler, - secureSettings, - lockscreenUserManager, - sensitiveNotifProtectionController, - sectionStyleProvider, - userTracker, - groupMembershipManager, - ) + private val adjustmentProvider = + NotifUiAdjustmentProvider( + handler, + secureSettings, + lockscreenUserManager, + sensitiveNotifProtectionController, + sectionStyleProvider, + userTracker, + groupMembershipManager, + ) @Before fun setup() { @@ -92,9 +91,8 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() { adjustmentProvider.addDirtyListener(dirtyListener) verify(secureSettings).getIntForUser(eq(SHOW_NOTIFICATION_SNOOZE), any(), any()) contentObserver = withArgCaptor { - verify(secureSettings).registerContentObserverForUser( - eq(SHOW_NOTIFICATION_SNOOZE), capture(), any() - ) + verify(secureSettings) + .registerContentObserverForUserSync(eq(SHOW_NOTIFICATION_SNOOZE), capture(), any()) } verifyNoMoreInteractions(secureSettings, dirtyListener) } 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 7903a731c1d0..e984200c305e 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 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -110,7 +111,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldNotHeadsUp( @@ -129,7 +131,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -146,7 +149,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -163,7 +167,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -180,7 +185,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -197,7 +203,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { assertFsiNotSuppressed() } @@ -208,7 +215,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro avalancheProvider.startTime = whenAgo(10) withFilter( - AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager) + AvalancheSuppressor(avalancheProvider, systemClock, systemSettings, packageManager, + uiEventLogger) ) { ensurePeekState() assertShouldHeadsUp( @@ -232,7 +240,8 @@ class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionPro ).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 a355cd16ee15..0d3ab865669e 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 @@ -27,6 +27,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -50,7 +51,6 @@ import android.widget.TextView; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; -import androidx.test.filters.Suppress; import com.android.systemui.SysuiTestCase; import com.android.systemui.media.controls.util.MediaFeatureFlag; @@ -81,7 +81,6 @@ import java.util.concurrent.TimeUnit; @SmallTest @RunWith(AndroidJUnit4.class) @RunWithLooper(setAsMainLooper = true) -@Suppress public class NotificationContentInflaterTest extends SysuiTestCase { private NotificationContentInflater mNotificationInflater; @@ -128,7 +127,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { TestableLooper.get(this)); ExpandableNotificationRow row = mHelper.createRow(mBuilder.build()); mRow = spy(row); - when(mNotifLayoutInflaterFactoryProvider.provide(any(), any())) + when(mNotifLayoutInflaterFactoryProvider.provide(any(), anyInt())) .thenReturn(mNotifLayoutInflaterFactory); mNotificationInflater = new NotificationContentInflater( @@ -140,7 +139,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { mSmartReplyStateInflater, mNotifLayoutInflaterFactoryProvider, mHeadsUpStyleProvider, - mock(NotificationContentInflaterLogger.class)); + mock(NotificationRowContentBinderLogger.class)); } @Test @@ -265,7 +264,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { R.layout.custom_view_dark); } }, - mock(NotificationContentInflaterLogger.class)); + mock(NotificationRowContentBinderLogger.class)); assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS)); } @@ -282,6 +281,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { } @Test + @Ignore public void testUsesSameViewWhenCachedPossibleToReuse() throws Exception { // GIVEN a cached view. RemoteViews contractedRemoteView = mBuilder.createContentView(); @@ -347,6 +347,7 @@ public class NotificationContentInflaterTest extends SysuiTestCase { } @Test + @Ignore public void testNotificationViewHeightTooSmallFailsValidation() { View view = mock(View.class); when(view.getHeight()) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt index 352b79f50b90..310fa67a3c6b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationSettingsControllerTest.kt @@ -102,6 +102,7 @@ class NotificationSettingsControllerTest : SysuiTestCase() { verify(userTracker).addCallback(any(), any()) verify(dumpManager).registerNormalDumpable(anyString(), eq(controller)) } + @Test fun updateContentObserverRegistration_onUserChange_noSettingsListeners() { verify(userTracker).addCallback(capture(userTrackerCallbackCaptor), any()) @@ -112,10 +113,11 @@ class NotificationSettingsControllerTest : SysuiTestCase() { userCallback.onUserChanged(userId, context) // Validate: Nothing to do, since we aren't monitoring settings - verify(secureSettings, never()).unregisterContentObserver(any()) + verify(secureSettings, never()).unregisterContentObserverSync(any()) verify(secureSettings, never()) - .registerContentObserverForUser(any(Uri::class.java), anyBoolean(), any(), anyInt()) + .registerContentObserverForUserSync(any(Uri::class.java), anyBoolean(), any(), anyInt()) } + @Test fun updateContentObserverRegistration_onUserChange_withSettingsListeners() { // When: someone is listening to a setting @@ -129,9 +131,9 @@ class NotificationSettingsControllerTest : SysuiTestCase() { userCallback.onUserChanged(userId, context) // Validate: The tracker is unregistered and re-registered with the new user - verify(secureSettings).unregisterContentObserver(any()) + verify(secureSettings).unregisterContentObserverSync(any()) verify(secureSettings) - .registerContentObserverForUser(eq(settingUri1), eq(false), any(), eq(userId)) + .registerContentObserverForUserSync(eq(settingUri1), eq(false), any(), eq(userId)) } @Test @@ -140,7 +142,7 @@ class NotificationSettingsControllerTest : SysuiTestCase() { verifyZeroInteractions(secureSettings) testableLooper.processAllMessages() verify(secureSettings) - .registerContentObserverForUser( + .registerContentObserverForUserSync( eq(settingUri1), eq(false), any(), @@ -149,7 +151,7 @@ class NotificationSettingsControllerTest : SysuiTestCase() { controller.addCallback(settingUri1, Mockito.mock(Listener::class.java)) verify(secureSettings) - .registerContentObserverForUser(any(Uri::class.java), anyBoolean(), any(), anyInt()) + .registerContentObserverForUserSync(any(Uri::class.java), anyBoolean(), any(), anyInt()) } @Test @@ -158,7 +160,7 @@ class NotificationSettingsControllerTest : SysuiTestCase() { verifyZeroInteractions(secureSettings) testableLooper.processAllMessages() verify(secureSettings) - .registerContentObserverForUser( + .registerContentObserverForUserSync( eq(settingUri1), eq(false), any(), @@ -170,7 +172,7 @@ class NotificationSettingsControllerTest : SysuiTestCase() { verifyNoMoreInteractions(secureSettings) testableLooper.processAllMessages() verify(secureSettings) - .registerContentObserverForUser( + .registerContentObserverForUserSync( eq(settingUri2), eq(false), any(), @@ -186,7 +188,7 @@ class NotificationSettingsControllerTest : SysuiTestCase() { verifyZeroInteractions(secureSettings) testableLooper.processAllMessages() verify(secureSettings) - .registerContentObserverForUser( + .registerContentObserverForUserSync( eq(settingUri1), eq(false), any(), @@ -198,18 +200,18 @@ class NotificationSettingsControllerTest : SysuiTestCase() { verifyNoMoreInteractions(secureSettings) testableLooper.processAllMessages() verify(secureSettings) - .registerContentObserverForUser(eq(settingUri2), anyBoolean(), any(), anyInt()) + .registerContentObserverForUserSync(eq(settingUri2), anyBoolean(), any(), anyInt()) clearInvocations(secureSettings) controller.removeCallback(settingUri2, listenerSetting2) testableLooper.processAllMessages() - verify(secureSettings, never()).unregisterContentObserver(any()) + verify(secureSettings, never()).unregisterContentObserverSync(any()) clearInvocations(secureSettings) controller.removeCallback(settingUri1, listenerSetting1) verifyNoMoreInteractions(secureSettings) testableLooper.processAllMessages() - verify(secureSettings).unregisterContentObserver(any()) + verify(secureSettings).unregisterContentObserverSync(any()) } @Test @@ -253,7 +255,7 @@ class NotificationSettingsControllerTest : SysuiTestCase() { testableLooper.processAllMessages() verify(secureSettings) - .registerContentObserverForUser( + .registerContentObserverForUserSync( any(Uri::class.java), anyBoolean(), capture(settingsObserverCaptor), 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 16618602e2c3..65941adf280e 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 @@ public class NotificationTestHelper { mBgCoroutineContext, mMainCoroutineContext); - NotificationContentInflater contentBinder = new NotificationContentInflater( + NotificationRowContentBinder contentBinder = new NotificationContentInflater( mock(NotifRemoteViewCache.class), mock(NotificationRemoteInputManager.class), mock(ConversationNotificationProcessor.class), @@ -201,7 +201,7 @@ public class NotificationTestHelper { 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 e025d3d36ab1..d3666321c8e4 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 @@ class SingleLineViewInflaterTest : SysuiTestCase() { } // 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/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index cb9790b2495a..7ea85a199406 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -133,6 +133,7 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.res.R; import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor; +import com.android.systemui.scene.domain.startable.ScrimStartable; import com.android.systemui.scene.shared.flag.SceneContainerFlag; import com.android.systemui.settings.UserTracker; import com.android.systemui.settings.brightness.BrightnessSliderController; @@ -254,7 +255,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Mock private IDreamManager mDreamManager; @Mock private LightRevealScrimViewModel mLightRevealScrimViewModel; @Mock private LightRevealScrim mLightRevealScrim; - @Mock private ScrimController mScrimController; @Mock private DozeScrimController mDozeScrimController; @Mock private Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; @Mock private BiometricUnlockController mBiometricUnlockController; @@ -355,6 +355,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { private final BrightnessMirrorShowingInteractor mBrightnessMirrorShowingInteractor = mKosmos.getBrightnessMirrorShowingInteractor(); + private ScrimController mScrimController; @Before public void setup() throws Exception { @@ -472,6 +473,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mUserTracker.getUserHandle()).thenReturn( UserHandle.of(ActivityManager.getCurrentUser())); + mScrimController = mKosmos.getScrimController(); + createCentralSurfaces(); } @@ -733,7 +736,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @Test public void testFingerprintNotification_UpdatesScrims() { mCentralSurfaces.notifyBiometricAuthModeChanged(); - verify(mScrimController).transitionTo(any(), any()); + verify(mScrimController).legacyTransitionTo(any(), any()); } @Test @@ -742,7 +745,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { when(mBiometricUnlockController.getMode()) .thenReturn(BiometricUnlockController.MODE_WAKE_AND_UNLOCK); mCentralSurfaces.updateScrimController(); - verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any()); + verify(mScrimController).legacyTransitionTo(eq(ScrimState.UNLOCKED), any()); } @Test @@ -753,7 +756,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // Starting a pulse should change the scrim controller to the pulsing state when(mCameraLauncher.isLaunchingAffordance()).thenReturn(true); mCentralSurfaces.updateScrimController(); - verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any()); + verify(mScrimController).legacyTransitionTo(eq(ScrimState.UNLOCKED), any()); } @Test @@ -789,7 +792,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // Starting a pulse should change the scrim controller to the pulsing state when(mCameraLauncher.isLaunchingAffordance()).thenReturn(false); mCentralSurfaces.updateScrimController(); - verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD)); + verify(mScrimController).legacyTransitionTo(eq(ScrimState.KEYGUARD)); } @Test @@ -817,12 +820,12 @@ public class CentralSurfacesImplTest extends SysuiTestCase { // Starting a pulse should change the scrim controller to the pulsing state when(mDozeServiceHost.isPulsing()).thenReturn(true); mCentralSurfaces.updateScrimController(); - verify(mScrimController).transitionTo(eq(ScrimState.PULSING), any()); + verify(mScrimController).legacyTransitionTo(eq(ScrimState.PULSING), any()); // Ending a pulse should take it back to keyguard state when(mDozeServiceHost.isPulsing()).thenReturn(false); mCentralSurfaces.updateScrimController(); - verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD)); + verify(mScrimController).legacyTransitionTo(eq(ScrimState.KEYGUARD)); } @Test @@ -833,7 +836,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCentralSurfaces.updateScrimController(); - verify(mScrimController, times(2)).transitionTo(eq(ScrimState.AOD)); + verify(mScrimController, times(2)).legacyTransitionTo(eq(ScrimState.AOD)); verify(mStatusBarKeyguardViewManager).onKeyguardFadedAway(); } @@ -845,7 +848,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCentralSurfaces.updateScrimController(); - verify(mScrimController).transitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE)); + verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE)); verify(mStatusBarKeyguardViewManager).onKeyguardFadedAway(); } @@ -861,7 +864,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCentralSurfaces.updateScrimController(); - verify(mScrimController).transitionTo(eq(ScrimState.AUTH_SCRIMMED)); + verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED)); } @Test @@ -877,7 +880,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCentralSurfaces.updateScrimController(); // Tests the safeguard to reset the scrimstate - verify(mScrimController, never()).transitionTo(any()); + verify(mScrimController, never()).legacyTransitionTo(any()); } @Test @@ -893,7 +896,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCentralSurfaces.updateScrimController(); // Tests the safeguard to reset the scrimstate - verify(mScrimController, never()).transitionTo(eq(ScrimState.KEYGUARD)); + verify(mScrimController, never()).legacyTransitionTo(eq(ScrimState.KEYGUARD)); } @Test @@ -908,7 +911,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mCentralSurfaces.updateScrimController(); - verify(mScrimController).transitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE)); + verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE)); } @Test @@ -920,7 +923,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mTestScope.getTestScheduler().runCurrent(); // ScrimState also transitions. - verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB); + verify(mScrimController).legacyTransitionTo(ScrimState.GLANCEABLE_HUB); // Transition away from the glanceable hub. mKosmos.getCommunalRepository() @@ -929,7 +932,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mTestScope.getTestScheduler().runCurrent(); // ScrimState goes back to UNLOCKED. - verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any()); + verify(mScrimController).legacyTransitionTo(eq(ScrimState.UNLOCKED), any()); } @Test @@ -945,7 +948,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mTestScope.getTestScheduler().runCurrent(); // ScrimState also transitions. - verify(mScrimController).transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); + verify(mScrimController).legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); // Transition away from the glanceable hub. mKosmos.getCommunalRepository() @@ -954,7 +957,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mTestScope.getTestScheduler().runCurrent(); // ScrimState goes back to UNLOCKED. - verify(mScrimController).transitionTo(eq(ScrimState.DREAMING)); + verify(mScrimController).legacyTransitionTo(eq(ScrimState.DREAMING)); } @Test @@ -1164,6 +1167,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase { @EnableSceneContainer public void brightnesShowingChanged_flagEnabled_ScrimControllerNotified() { mCentralSurfaces.registerCallbacks(); + final ScrimStartable scrimStartable = mKosmos.getScrimStartable(); + scrimStartable.start(); mBrightnessMirrorShowingInteractor.setMirrorShowing(true); mTestScope.getTestScheduler().runCurrent(); @@ -1173,7 +1178,7 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mTestScope.getTestScheduler().runCurrent(); ArgumentCaptor<ScrimState> captor = ArgumentCaptor.forClass(ScrimState.class); // The default is to call the one with the callback argument - verify(mScrimController, atLeastOnce()).transitionTo(captor.capture(), any()); + verify(mScrimController, atLeastOnce()).transitionTo(captor.capture()); assertThat(captor.getValue()).isNotEqualTo(ScrimState.BRIGHTNESS_MIRROR); } @@ -1184,8 +1189,9 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mBrightnessMirrorShowingInteractor.setMirrorShowing(true); mTestScope.getTestScheduler().runCurrent(); - verify(mScrimController, never()).transitionTo(ScrimState.BRIGHTNESS_MIRROR); - verify(mScrimController, never()).transitionTo(eq(ScrimState.BRIGHTNESS_MIRROR), any()); + verify(mScrimController, never()).legacyTransitionTo(ScrimState.BRIGHTNESS_MIRROR); + verify(mScrimController, never()) + .legacyTransitionTo(eq(ScrimState.BRIGHTNESS_MIRROR), any()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 416a869bf2f0..4590071f0a2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -309,7 +309,7 @@ public class ScrimControllerTest extends SysuiTestCase { mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false); mTestScope.getTestScheduler().runCurrent(); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); finishAnimationsImmediately(); } @@ -327,7 +327,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToKeyguard() { - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); finishAnimationsImmediately(); assertScrimAlpha(Map.of( @@ -342,7 +342,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToShadeLocked() { - mScrimController.transitionTo(SHADE_LOCKED); + mScrimController.legacyTransitionTo(SHADE_LOCKED); mScrimController.setQsPosition(1f, 0); finishAnimationsImmediately(); @@ -360,7 +360,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToShadeLocked_clippingQs() { mScrimController.setClipsQsScrim(true); - mScrimController.transitionTo(SHADE_LOCKED); + mScrimController.legacyTransitionTo(SHADE_LOCKED); mScrimController.setQsPosition(1f, 0); finishAnimationsImmediately(); @@ -377,7 +377,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToOff() { - mScrimController.transitionTo(ScrimState.OFF); + mScrimController.legacyTransitionTo(ScrimState.OFF); finishAnimationsImmediately(); assertScrimAlpha(Map.of( @@ -394,7 +394,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToAod_withRegularWallpaper() { - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); assertScrimAlpha(Map.of( @@ -414,7 +414,7 @@ public class ScrimControllerTest extends SysuiTestCase { mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true); mTestScope.getTestScheduler().runCurrent(); - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); assertScrimAlpha(Map.of( @@ -423,7 +423,7 @@ public class ScrimControllerTest extends SysuiTestCase { assertEquals(0f, mScrimController.getState().getMaxLightRevealScrimAlpha(), 0f); // Pulsing notification should conserve AOD wallpaper. - mScrimController.transitionTo(ScrimState.PULSING); + mScrimController.legacyTransitionTo(ScrimState.PULSING); finishAnimationsImmediately(); assertScrimAlpha(Map.of( @@ -438,7 +438,7 @@ public class ScrimControllerTest extends SysuiTestCase { mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true); mTestScope.getTestScheduler().runCurrent(); - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); assertScrimAlpha(Map.of( @@ -457,7 +457,7 @@ public class ScrimControllerTest extends SysuiTestCase { mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true); mTestScope.getTestScheduler().runCurrent(); - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); mScrimController.setHasBackdrop(true); finishAnimationsImmediately(); @@ -476,7 +476,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToAod_withFrontAlphaUpdates() { // Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state. - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); mScrimController.setAodFrontScrimAlpha(0.5f); finishAnimationsImmediately(); @@ -485,7 +485,7 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimBehind, SEMI_TRANSPARENT)); // ... but that it does take effect once we enter the AOD state. - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); assertScrimAlpha(Map.of( mScrimInFront, SEMI_TRANSPARENT, @@ -501,8 +501,8 @@ public class ScrimControllerTest extends SysuiTestCase { // ... and make sure we recall the previous front scrim alpha even if we transition away // for a bit. - mScrimController.transitionTo(ScrimState.UNLOCKED); - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); assertScrimAlpha(Map.of( mScrimInFront, OPAQUE, @@ -512,8 +512,8 @@ public class ScrimControllerTest extends SysuiTestCase { // ... and alpha updates should be completely ignored if always_on is off. // Passing it forward would mess up the wake-up transition. mAlwaysOnEnabled = false; - mScrimController.transitionTo(ScrimState.UNLOCKED); - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); mScrimController.setAodFrontScrimAlpha(0.3f); assertEquals(ScrimState.AOD.getFrontAlpha(), mScrimInFront.getViewAlpha(), 0.001f); @@ -523,7 +523,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToAod_afterDocked_ignoresAlwaysOnAndUpdatesFrontAlpha() { // Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state. - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); mScrimController.setAodFrontScrimAlpha(0.5f); finishAnimationsImmediately(); @@ -533,7 +533,7 @@ public class ScrimControllerTest extends SysuiTestCase { // ... and doesn't take effect when disabled always_on mAlwaysOnEnabled = false; - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); assertScrimAlpha(Map.of( mScrimInFront, OPAQUE, @@ -542,9 +542,9 @@ public class ScrimControllerTest extends SysuiTestCase { // ... but will take effect after docked when(mDockManager.isDocked()).thenReturn(true); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); mScrimController.setAodFrontScrimAlpha(0.5f); - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); assertScrimAlpha(Map.of( @@ -572,14 +572,14 @@ public class ScrimControllerTest extends SysuiTestCase { mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false); mTestScope.getTestScheduler().runCurrent(); - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, mScrimBehind, TRANSPARENT)); assertEquals(1f, mScrimController.getState().getMaxLightRevealScrimAlpha(), 0f); - mScrimController.transitionTo(ScrimState.PULSING); + mScrimController.legacyTransitionTo(ScrimState.PULSING); finishAnimationsImmediately(); // Front scrim should be transparent, but tinted // Back scrim should be semi-transparent so the user can see the wallpaper @@ -617,7 +617,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToKeyguardBouncer() { - mScrimController.transitionTo(BOUNCER); + mScrimController.legacyTransitionTo(BOUNCER); finishAnimationsImmediately(); // Front scrim should be transparent // Back scrim should be visible and tinted to the surface color @@ -638,7 +638,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void lockscreenToHubTransition_setsBehindScrimAlpha() { // Start on lockscreen. - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); finishAnimationsImmediately(); // Behind scrim starts at default alpha. @@ -684,7 +684,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void hubToLockscreenTransition_setsViewAlpha() { // Start on glanceable hub. - mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB); + mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB); finishAnimationsImmediately(); // Behind scrim starts at 0 alpha. @@ -731,7 +731,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void transitionToHub() { mScrimController.setRawPanelExpansionFraction(0f); mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN); - mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB); + mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB); finishAnimationsImmediately(); // All scrims transparent on the hub. @@ -743,7 +743,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void openBouncerOnHub() { - mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB); + mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB); // Open the bouncer. mScrimController.setRawPanelExpansionFraction(0f); @@ -760,7 +760,7 @@ public class ScrimControllerTest extends SysuiTestCase { // Bouncer is closed. mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN); - mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB); + mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB); finishAnimationsImmediately(); // All scrims are transparent. @@ -772,7 +772,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void openShadeOnHub() { - mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB); + mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB); // Open the shade. mScrimController.setQsPosition(1f, 0); @@ -802,7 +802,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void transitionToHubOverDream() { mScrimController.setRawPanelExpansionFraction(0f); mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN); - mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); + mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); finishAnimationsImmediately(); // All scrims transparent on the hub. @@ -814,7 +814,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void openBouncerOnHubOverDream() { - mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); + mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); // Open the bouncer. mScrimController.setRawPanelExpansionFraction(0f); @@ -831,7 +831,7 @@ public class ScrimControllerTest extends SysuiTestCase { // Bouncer is closed. mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN); - mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); + mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); finishAnimationsImmediately(); // All scrims are transparent. @@ -843,7 +843,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void openShadeOnHubOverDream() { - mScrimController.transitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); + mScrimController.legacyTransitionTo(ScrimState.GLANCEABLE_HUB_OVER_DREAM); // Open the shade. mScrimController.setQsPosition(1f, 0); @@ -880,7 +880,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void onThemeChangeWhileClipQsScrim_bouncerBehindTint_remainsBlack() { mScrimController.setClipsQsScrim(true); - mScrimController.transitionTo(BOUNCER); + mScrimController.legacyTransitionTo(BOUNCER); finishAnimationsImmediately(); assertEquals(BOUNCER.getBehindTint(), Color.BLACK); @@ -892,7 +892,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToKeyguardBouncer_clippingQs() { mScrimController.setClipsQsScrim(true); - mScrimController.transitionTo(BOUNCER); + mScrimController.legacyTransitionTo(BOUNCER); finishAnimationsImmediately(); // Front scrim should be transparent // Back scrim should be clipping QS @@ -912,7 +912,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void disableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() { mScrimController.setClipsQsScrim(true); - mScrimController.transitionTo(BOUNCER); + mScrimController.legacyTransitionTo(BOUNCER); mScrimController.setClipsQsScrim(false); @@ -934,7 +934,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void enableClipQsScrimWithoutStateTransition_updatesTintAndAlpha() { mScrimController.setClipsQsScrim(false); - mScrimController.transitionTo(BOUNCER); + mScrimController.legacyTransitionTo(BOUNCER); mScrimController.setClipsQsScrim(true); @@ -955,7 +955,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToBouncer() { - mScrimController.transitionTo(ScrimState.BOUNCER_SCRIMMED); + mScrimController.legacyTransitionTo(ScrimState.BOUNCER_SCRIMMED); finishAnimationsImmediately(); assertScrimAlpha(Map.of( mScrimInFront, OPAQUE, @@ -970,7 +970,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void transitionToUnlocked_clippedQs() { mScrimController.setClipsQsScrim(true); mScrimController.setRawPanelExpansionFraction(0f); - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); finishAnimationsImmediately(); assertScrimTinted(Map.of( @@ -1000,7 +1000,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void transitionToUnlocked_nonClippedQs_followsLargeScreensInterpolator() { mScrimController.setClipsQsScrim(false); mScrimController.setRawPanelExpansionFraction(0f); - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); finishAnimationsImmediately(); assertScrimTinted(Map.of( @@ -1037,15 +1037,15 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void scrimStateCallback() { - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); finishAnimationsImmediately(); assertEquals(mScrimState, ScrimState.UNLOCKED); - mScrimController.transitionTo(BOUNCER); + mScrimController.legacyTransitionTo(BOUNCER); finishAnimationsImmediately(); assertEquals(mScrimState, BOUNCER); - mScrimController.transitionTo(ScrimState.BOUNCER_SCRIMMED); + mScrimController.legacyTransitionTo(ScrimState.BOUNCER_SCRIMMED); finishAnimationsImmediately(); assertEquals(mScrimState, ScrimState.BOUNCER_SCRIMMED); } @@ -1054,7 +1054,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void panelExpansion() { mScrimController.setRawPanelExpansionFraction(0f); mScrimController.setRawPanelExpansionFraction(0.5f); - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); finishAnimationsImmediately(); reset(mScrimBehind); @@ -1114,7 +1114,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void panelExpansionAffectsAlpha() { mScrimController.setRawPanelExpansionFraction(0f); mScrimController.setRawPanelExpansionFraction(0.5f); - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); finishAnimationsImmediately(); final float scrimAlpha = mScrimBehind.getViewAlpha(); @@ -1135,10 +1135,10 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToUnlockedFromOff() { // Simulate unlock with fingerprint without AOD - mScrimController.transitionTo(ScrimState.OFF); + mScrimController.legacyTransitionTo(ScrimState.OFF); mScrimController.setRawPanelExpansionFraction(0f); finishAnimationsImmediately(); - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); finishAnimationsImmediately(); @@ -1157,10 +1157,10 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void transitionToUnlockedFromAod() { // Simulate unlock with fingerprint - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); mScrimController.setRawPanelExpansionFraction(0f); finishAnimationsImmediately(); - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); finishAnimationsImmediately(); @@ -1179,9 +1179,9 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void scrimBlanksBeforeLeavingAod() { // Simulate unlock with fingerprint - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); - mScrimController.transitionTo(ScrimState.UNLOCKED, + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED, new ScrimController.Callback() { @Override public void onDisplayBlanked() { @@ -1203,9 +1203,9 @@ public class ScrimControllerTest extends SysuiTestCase { public void scrimBlankCallbackWhenUnlockingFromPulse() { boolean[] blanked = {false}; // Simulate unlock with fingerprint - mScrimController.transitionTo(ScrimState.PULSING); + mScrimController.legacyTransitionTo(ScrimState.PULSING); finishAnimationsImmediately(); - mScrimController.transitionTo(ScrimState.UNLOCKED, + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED, new ScrimController.Callback() { @Override public void onDisplayBlanked() { @@ -1251,16 +1251,16 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.setHasBackdrop(false); mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false); mTestScope.getTestScheduler().runCurrent(); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); finishAnimationsImmediately(); // WHEN Simulate unlock with fingerprint - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); // WHEN transitioning to UNLOCKED, onDisplayCallbackBlanked callback called to continue // the transition but the scrim was not actually blanked - mScrimController.transitionTo(ScrimState.UNLOCKED, + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED, new ScrimController.Callback() { @Override public void onDisplayBlanked() { @@ -1279,7 +1279,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void testScrimCallback() { int[] callOrder = {0, 0, 0}; int[] currentCall = {0}; - mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() { + mScrimController.legacyTransitionTo(ScrimState.AOD, new ScrimController.Callback() { @Override public void onStart() { callOrder[0] = ++currentCall[0]; @@ -1310,19 +1310,19 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testScrimCallbackCancelled() { boolean[] cancelledCalled = {false}; - mScrimController.transitionTo(ScrimState.AOD, new ScrimController.Callback() { + mScrimController.legacyTransitionTo(ScrimState.AOD, new ScrimController.Callback() { @Override public void onCancelled() { cancelledCalled[0] = true; } }); - mScrimController.transitionTo(ScrimState.PULSING); + mScrimController.legacyTransitionTo(ScrimState.PULSING); Assert.assertTrue("onCancelled should have been called", cancelledCalled[0]); } @Test public void testHoldsWakeLock_whenAOD() { - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); verify(mWakeLock).acquire(anyString()); verify(mWakeLock, never()).release(anyString()); finishAnimationsImmediately(); @@ -1331,24 +1331,24 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testDoesNotHoldWakeLock_whenUnlocking() { - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); finishAnimationsImmediately(); verifyZeroInteractions(mWakeLock); } @Test public void testCallbackInvokedOnSameStateTransition() { - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); finishAnimationsImmediately(); ScrimController.Callback callback = mock(ScrimController.Callback.class); - mScrimController.transitionTo(ScrimState.UNLOCKED, callback); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED, callback); verify(callback).onFinished(); } @Test public void testHoldsAodWallpaperAnimationLock() { // Pre-conditions - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); reset(mWakeLock); @@ -1362,7 +1362,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testHoldsPulsingWallpaperAnimationLock() { // Pre-conditions - mScrimController.transitionTo(ScrimState.PULSING); + mScrimController.legacyTransitionTo(ScrimState.PULSING); finishAnimationsImmediately(); reset(mWakeLock); @@ -1378,9 +1378,9 @@ public class ScrimControllerTest extends SysuiTestCase { mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true); mTestScope.getTestScheduler().runCurrent(); - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any()); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); verify(mAlarmManager).cancel(any(AlarmManager.OnAlarmListener.class)); } @@ -1391,22 +1391,22 @@ public class ScrimControllerTest extends SysuiTestCase { mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true); mTestScope.getTestScheduler().runCurrent(); - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); verify(mAlarmManager).setExact(anyInt(), anyLong(), any(), any(), any()); } @Test public void testConservesExpansionOpacityAfterTransition() { - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); mScrimController.setRawPanelExpansionFraction(0.5f); finishAnimationsImmediately(); final float expandedAlpha = mScrimBehind.getViewAlpha(); - mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR); + mScrimController.legacyTransitionTo(ScrimState.BRIGHTNESS_MIRROR); finishAnimationsImmediately(); - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); finishAnimationsImmediately(); assertEquals("Scrim expansion opacity wasn't conserved when transitioning back", @@ -1415,12 +1415,12 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testCancelsOldAnimationBeforeBlanking() { - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); // Consume whatever value we had before mAnimatorListener.reset(); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); finishAnimationsImmediately(); Assert.assertTrue("Animators not canceled", mAnimatorListener.getNumCancels() != 0); } @@ -1439,13 +1439,13 @@ public class ScrimControllerTest extends SysuiTestCase { mTestScope.getTestScheduler().runCurrent(); mScrimController.setKeyguardOccluded(true); - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, mScrimBehind, OPAQUE)); - mScrimController.transitionTo(ScrimState.PULSING); + mScrimController.legacyTransitionTo(ScrimState.PULSING); finishAnimationsImmediately(); assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, @@ -1457,7 +1457,7 @@ public class ScrimControllerTest extends SysuiTestCase { mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true); mTestScope.getTestScheduler().runCurrent(); - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); finishAnimationsImmediately(); assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, @@ -1478,7 +1478,7 @@ public class ScrimControllerTest extends SysuiTestCase { if (state == ScrimState.UNINITIALIZED) { continue; } - mScrimController.transitionTo(state); + mScrimController.legacyTransitionTo(state); finishAnimationsImmediately(); assertEquals("Should be clickable unless AOD or PULSING, was: " + state, mScrimBehind.getViewAlpha() != 0 && !eatsTouches.contains(state), @@ -1520,7 +1520,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testScrimsOpaque_whenShadeFullyExpanded() { - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); mScrimController.setRawPanelExpansionFraction(1); // notifications scrim alpha change require calling setQsPosition mScrimController.setQsPosition(0, 300); @@ -1536,7 +1536,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void testAuthScrim_setClipQSScrimTrue_notifScrimOpaque_whenShadeFullyExpanded() { // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen // with the camera app occluding the keyguard) - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); mScrimController.setClipsQsScrim(true); mScrimController.setRawPanelExpansionFraction(1); // notifications scrim alpha change require calling setQsPosition @@ -1544,7 +1544,7 @@ public class ScrimControllerTest extends SysuiTestCase { finishAnimationsImmediately(); // WHEN the user triggers the auth bouncer - mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE); + mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE); finishAnimationsImmediately(); assertEquals("Behind scrim should be opaque", @@ -1564,7 +1564,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void testAuthScrim_setClipQSScrimFalse_notifScrimOpaque_whenShadeFullyExpanded() { // GIVEN device has an activity showing ('UNLOCKED' state can occur on the lock screen // with the camera app occluding the keyguard) - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); mScrimController.setClipsQsScrim(false); mScrimController.setRawPanelExpansionFraction(1); // notifications scrim alpha change require calling setQsPosition @@ -1572,7 +1572,7 @@ public class ScrimControllerTest extends SysuiTestCase { finishAnimationsImmediately(); // WHEN the user triggers the auth bouncer - mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED_SHADE); + mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE); finishAnimationsImmediately(); assertEquals("Behind scrim should be opaque", @@ -1590,11 +1590,11 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testAuthScrimKeyguard() { // GIVEN device is on the keyguard - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); finishAnimationsImmediately(); // WHEN the user triggers the auth bouncer - mScrimController.transitionTo(ScrimState.AUTH_SCRIMMED); + mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED); finishAnimationsImmediately(); // THEN the front scrim is updated and the KEYGUARD scrims are the same as the @@ -1608,7 +1608,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testScrimsVisible_whenShadeVisible() { mScrimController.setClipsQsScrim(true); - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); mScrimController.setRawPanelExpansionFraction(0.3f); // notifications scrim alpha change require calling setQsPosition mScrimController.setQsPosition(0, 300); @@ -1643,7 +1643,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testScrimsVisible_whenShadeVisible_clippingQs() { mScrimController.setClipsQsScrim(true); - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); mScrimController.setRawPanelExpansionFraction(0.3f); // notifications scrim alpha change require calling setQsPosition mScrimController.setQsPosition(0.5f, 300); @@ -1657,7 +1657,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testScrimsVisible_whenShadeVisibleOnLockscreen() { - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); mScrimController.setQsPosition(0.25f, 300); assertScrimAlpha(Map.of( @@ -1668,7 +1668,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testNotificationScrimTransparent_whenOnLockscreen() { - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); // even if shade is not pulled down, panel has expansion of 1 on the lockscreen mScrimController.setRawPanelExpansionFraction(1); mScrimController.setQsPosition(0f, /*qs panel bottom*/ 0); @@ -1681,7 +1681,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testNotificationScrimVisible_afterOpeningShadeFromLockscreen() { mScrimController.setRawPanelExpansionFraction(1); - mScrimController.transitionTo(SHADE_LOCKED); + mScrimController.legacyTransitionTo(SHADE_LOCKED); finishAnimationsImmediately(); assertScrimAlpha(Map.of( @@ -1695,7 +1695,7 @@ public class ScrimControllerTest extends SysuiTestCase { // clipping doesn't change tested logic but allows to assert scrims more in line with // their expected large screen behaviour mScrimController.setClipsQsScrim(false); - mScrimController.transitionTo(SHADE_LOCKED); + mScrimController.legacyTransitionTo(SHADE_LOCKED); mScrimController.setQsPosition(1f, 100 /* value doesn't matter */); assertTintAfterExpansion(mScrimBehind, SHADE_LOCKED.getBehindTint(), /* expansion= */ 1f); @@ -1709,7 +1709,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void expansionNotificationAlpha_shadeLocked_bouncerActive_usesBouncerInterpolator() { when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true); - mScrimController.transitionTo(SHADE_LOCKED); + mScrimController.legacyTransitionTo(SHADE_LOCKED); float expansion = 0.8f; float expectedAlpha = @@ -1725,7 +1725,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void expansionNotificationAlpha_shadeLocked_bouncerNotActive_usesShadeInterpolator() { when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false); - mScrimController.transitionTo(SHADE_LOCKED); + mScrimController.legacyTransitionTo(SHADE_LOCKED); float expansion = 0.8f; float expectedAlpha = ShadeInterpolation.getNotificationScrimAlpha(expansion); @@ -1740,7 +1740,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void notificationAlpha_unnocclusionAnimating_bouncerNotActive_usesKeyguardNotifAlpha() { when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); assertAlphaAfterExpansion( mNotificationsScrim, ScrimState.KEYGUARD.getNotifAlpha(), /* expansion */ 0f); @@ -1760,7 +1760,7 @@ public class ScrimControllerTest extends SysuiTestCase { when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(true); mScrimController.setClipsQsScrim(true); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); float expansion = 0.8f; float alpha = 1 - BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(expansion); @@ -1780,7 +1780,7 @@ public class ScrimControllerTest extends SysuiTestCase { when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false); mScrimController.setClipsQsScrim(true); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); float expansion = 0.8f; float alpha = 1 - ShadeInterpolation.getNotificationScrimAlpha(expansion); @@ -1800,7 +1800,7 @@ public class ScrimControllerTest extends SysuiTestCase { when(mStatusBarKeyguardViewManager.isPrimaryBouncerInTransit()).thenReturn(false); mScrimController.setClipsQsScrim(false); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); finishAnimationsImmediately(); assertThat(mScrimBehind.getTint()) .isEqualTo(ScrimState.KEYGUARD.getBehindTint()); @@ -1810,7 +1810,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void testNotificationTransparency_followsTransitionToFullShade() { mScrimController.setClipsQsScrim(true); - mScrimController.transitionTo(SHADE_LOCKED); + mScrimController.legacyTransitionTo(SHADE_LOCKED); mScrimController.setRawPanelExpansionFraction(1.0f); finishAnimationsImmediately(); @@ -1821,7 +1821,7 @@ public class ScrimControllerTest extends SysuiTestCase { )); float shadeLockedAlpha = mNotificationsScrim.getViewAlpha(); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); mScrimController.setRawPanelExpansionFraction(1.0f); finishAnimationsImmediately(); float keyguardAlpha = mNotificationsScrim.getViewAlpha(); @@ -1849,10 +1849,10 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void notificationTransparency_followsNotificationScrimProgress() { - mScrimController.transitionTo(SHADE_LOCKED); + mScrimController.legacyTransitionTo(SHADE_LOCKED); mScrimController.setRawPanelExpansionFraction(1.0f); finishAnimationsImmediately(); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); mScrimController.setRawPanelExpansionFraction(1.0f); finishAnimationsImmediately(); @@ -1866,7 +1866,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void notificationAlpha_qsNotClipped_alphaMatchesNotificationExpansionProgress() { mScrimController.setClipsQsScrim(false); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); // RawPanelExpansion and QsExpansion are usually used for the notification alpha // calculation. // Here we set them to non-zero values explicitly to make sure that in not clipped mode, @@ -1927,7 +1927,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void notificationBoundsTopGetsPassedToKeyguard() { - mScrimController.transitionTo(SHADE_LOCKED); + mScrimController.legacyTransitionTo(SHADE_LOCKED); mScrimController.setQsPosition(1f, 0); finishAnimationsImmediately(); @@ -1938,7 +1938,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void notificationBoundsTopDoesNotGetPassedToKeyguardWhenNotifScrimIsNotVisible() { mScrimController.setKeyguardOccluded(true); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); finishAnimationsImmediately(); mScrimController.setNotificationsBounds(0f, 100f, 0f, 0f); @@ -1949,7 +1949,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void transitionToDreaming() { mScrimController.setRawPanelExpansionFraction(0f); mScrimController.setBouncerHiddenFraction(KeyguardBouncerConstants.EXPANSION_HIDDEN); - mScrimController.transitionTo(ScrimState.DREAMING); + mScrimController.legacyTransitionTo(ScrimState.DREAMING); finishAnimationsImmediately(); assertScrimAlpha(Map.of( @@ -1975,7 +1975,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void setUnOccludingAnimationKeyguard() { - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); finishAnimationsImmediately(); assertThat(mNotificationsScrim.getViewAlpha()) .isWithin(0.01f).of(ScrimState.KEYGUARD.getNotifAlpha()); @@ -1990,14 +1990,14 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testHidesScrimFlickerInActivity() { mScrimController.setKeyguardOccluded(true); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); finishAnimationsImmediately(); assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, mScrimBehind, TRANSPARENT, mNotificationsScrim, TRANSPARENT)); - mScrimController.transitionTo(SHADE_LOCKED); + mScrimController.legacyTransitionTo(SHADE_LOCKED); finishAnimationsImmediately(); assertScrimAlpha(Map.of( mScrimInFront, TRANSPARENT, @@ -2008,7 +2008,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void notificationAlpha_inKeyguardState_bouncerNotActive_clipsQsScrimFalse() { mScrimController.setClipsQsScrim(false); - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); float expansion = 0.8f; assertAlphaAfterExpansion(mNotificationsScrim, 0f, expansion); @@ -2016,14 +2016,14 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void aodStateSetsFrontScrimToNotBlend() { - mScrimController.transitionTo(ScrimState.AOD); + mScrimController.legacyTransitionTo(ScrimState.AOD); assertFalse("Front scrim should not blend with main color", mScrimInFront.shouldBlendWithMainColor()); } @Test public void applyState_unlocked_bouncerShowing() { - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); mScrimController.setBouncerHiddenFraction(0.99f); mScrimController.setRawPanelExpansionFraction(0f); finishAnimationsImmediately(); @@ -2032,13 +2032,13 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void ignoreTransitionRequestWhileKeyguardTransitionRunning() { - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); mScrimController.mBouncerToGoneTransition.accept( new TransitionStep(KeyguardState.PRIMARY_BOUNCER, KeyguardState.GONE, 0f, TransitionState.RUNNING, "ScrimControllerTest")); // This request should not happen - mScrimController.transitionTo(ScrimState.BOUNCER); + mScrimController.legacyTransitionTo(ScrimState.BOUNCER); assertThat(mScrimController.getState()).isEqualTo(ScrimState.UNLOCKED); } @@ -2055,16 +2055,16 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testDoNotAnimateChangeIfOccludeAnimationPlaying() { mScrimController.setOccludeAnimationPlaying(true); - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); assertFalse(ScrimState.UNLOCKED.mAnimateChange); } @Test public void testNotifScrimAlpha_1f_afterUnlockFinishedAndExpanded() { - mScrimController.transitionTo(ScrimState.KEYGUARD); + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); when(mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()).thenReturn(true); - mScrimController.transitionTo(ScrimState.UNLOCKED); + mScrimController.legacyTransitionTo(ScrimState.UNLOCKED); mScrimController.onUnlockAnimationFinished(); assertAlphaAfterExpansion(mNotificationsScrim, 1f, 1f); } 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 eb2538ec032e..7d586cde2222 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 @@ class MobileRepositorySwitcherTest : SysuiTestCase() { 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 96e599f8f4d0..76982ae12516 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 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { 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 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { flags, testScope.backgroundScope, mainExecutor, - dispatcher, + testDispatcher, wifiPickerTrackerFactory, wifiManager, wifiLogBuffer, @@ -216,7 +216,7 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { fakeBroadcastDispatcher, connectivityManager, telephonyManager = telephonyManager, - bgDispatcher = dispatcher, + bgDispatcher = testDispatcher, logger = logger, mobileMappingsProxy = mobileMappings, scope = testScope.backgroundScope, @@ -249,8 +249,9 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { mobileMappings, fakeBroadcastDispatcher, context, - dispatcher, + /* bgDispatcher = */ testDispatcher, testScope.backgroundScope, + /* mainDispatcher = */ testDispatcher, airplaneModeRepository, wifiRepository, fullConnectionFactory, @@ -1225,8 +1226,9 @@ class MobileConnectionsRepositoryTest : SysuiTestCase() { 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 02f53b6846e8..d24d87c6f57a 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_UNAVAI 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 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { @Test fun satelliteNotSupported_listenersAreNotRegistered() = testScope.runTest { - setupDefaultRepo() // GIVEN satellite is not supported setUpRepo( uptime = MIN_UPTIME, @@ -345,6 +345,110 @@ class DeviceBasedSatelliteRepositoryImplTest : SysuiTestCase() { } @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/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt new file mode 100644 index 000000000000..16132ba7cff5 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/glowboxeffect/GlowBoxEffectTest.kt @@ -0,0 +1,124 @@ +/* + * 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.surfaceeffects.glowboxeffect + +import android.graphics.Color +import android.graphics.Paint +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.AnimatorTestRule +import com.android.systemui.surfaceeffects.PaintDrawCallback +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class GlowBoxEffectTest : SysuiTestCase() { + + @get:Rule val animatorTestRule = AnimatorTestRule(this) + private lateinit var config: GlowBoxConfig + private lateinit var glowBoxEffect: GlowBoxEffect + private lateinit var drawCallback: PaintDrawCallback + + @Before + fun setup() { + drawCallback = + object : PaintDrawCallback { + override fun onDraw(paint: Paint) {} + } + config = + GlowBoxConfig( + startCenterX = 0f, + startCenterY = 0f, + endCenterX = 0f, + endCenterY = 0f, + width = 1f, + height = 1f, + color = Color.WHITE, + blurAmount = 0.1f, + duration = 100L, + easeInDuration = 100L, + easeOutDuration = 100L + ) + glowBoxEffect = GlowBoxEffect(config, drawCallback) + } + + @Test + fun play_paintCallback_triggersDrawCallback() { + var paintFromCallback: Paint? = null + drawCallback = + object : PaintDrawCallback { + override fun onDraw(paint: Paint) { + paintFromCallback = paint + } + } + glowBoxEffect = GlowBoxEffect(config, drawCallback) + + assertThat(paintFromCallback).isNull() + + glowBoxEffect.play() + animatorTestRule.advanceTimeBy(50L) + + assertThat(paintFromCallback).isNotNull() + } + + @Test + fun play_followsAnimationStateInOrder() { + assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.NOT_PLAYING) + + glowBoxEffect.play() + + assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.EASE_IN) + + animatorTestRule.advanceTimeBy(config.easeInDuration + 50L) + + assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.MAIN) + + animatorTestRule.advanceTimeBy(config.duration + 50L) + + assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.EASE_OUT) + + animatorTestRule.advanceTimeBy(config.easeOutDuration + 50L) + + assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.NOT_PLAYING) + } + + @Test + fun finish_statePlaying_finishesAnimation() { + assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.NOT_PLAYING) + + glowBoxEffect.play() + glowBoxEffect.finish() + + assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.EASE_OUT) + } + + @Test + fun finish_stateNotPlaying_doesNotFinishAnimation() { + assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.NOT_PLAYING) + + glowBoxEffect.finish() + + assertThat(glowBoxEffect.state).isEqualTo(GlowBoxEffect.AnimationState.NOT_PLAYING) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt index 6f589418cf1e..41d7fd54902d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/surfaceeffects/loadingeffect/LoadingEffectTest.kt @@ -21,8 +21,8 @@ import android.graphics.RenderEffect import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase import com.android.systemui.animation.AnimatorTestRule -import com.android.systemui.model.SysUiStateTest import com.android.systemui.surfaceeffects.PaintDrawCallback import com.android.systemui.surfaceeffects.RenderEffectDrawCallback import com.android.systemui.surfaceeffects.turbulencenoise.TurbulenceNoiseAnimationConfig @@ -35,7 +35,7 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) -class LoadingEffectTest : SysUiStateTest() { +class LoadingEffectTest : SysuiTestCase() { @get:Rule val animatorTestRule = AnimatorTestRule(this) diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt index 1466d24accee..664f2df62782 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.temporarydisplay.chipbar import android.os.PowerManager import android.os.VibrationAttributes import android.os.VibrationEffect -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.MotionEvent import android.view.View @@ -29,6 +28,7 @@ import android.view.accessibility.AccessibilityManager import android.widget.ImageView import android.widget.TextView import androidx.core.animation.doOnCancel +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 @@ -68,7 +68,7 @@ import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper class ChipbarCoordinatorTest : SysuiTestCase() { private lateinit var underTest: ChipbarCoordinator diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java index 8f4cbafecbcd..31ee8589698e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayApplierTest.java @@ -45,9 +45,9 @@ import android.content.om.OverlayInfo; import android.content.om.OverlayManager; import android.content.om.OverlayManagerTransaction; import android.os.UserHandle; -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; @@ -69,7 +69,7 @@ import java.util.Map; import java.util.Set; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class ThemeOverlayApplierTest extends SysuiTestCase { private static final String TEST_DISABLED_PREFIX = "com.example."; diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java index 5ad88ad1f47c..53e033ef7c93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java @@ -52,9 +52,9 @@ import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import androidx.annotation.VisibleForTesting; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -88,7 +88,7 @@ import java.util.Map; import java.util.concurrent.Executor; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class ThemeOverlayControllerTest extends SysuiTestCase { private static final int USER_SYSTEM = UserHandle.USER_SYSTEM; @@ -202,7 +202,7 @@ public class ThemeOverlayControllerTest extends SysuiTestCase { verify(mWakefulnessLifecycle).addObserver(mWakefulnessLifecycleObserver.capture()); verify(mDumpManager).registerDumpable(any(), any()); verify(mDeviceProvisionedController).addCallback(mDeviceProvisionedListener.capture()); - verify(mSecureSettings).registerContentObserverForUser( + verify(mSecureSettings).registerContentObserverForUserSync( eq(Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES), eq(false), mSettingsObserver.capture(), eq(UserHandle.USER_ALL) ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java index 0581e0ea7b6e..8df37ceb2126 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/toast/ToastUITest.java @@ -46,7 +46,6 @@ import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.os.UserHandle; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.Display; import android.view.LayoutInflater; @@ -59,6 +58,7 @@ import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.util.IntPair; @@ -80,7 +80,7 @@ import org.mockito.stubbing.Answer; import java.util.Arrays; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class ToastUITest extends SysuiTestCase { private static final int ANDROID_UID = 1000; diff --git a/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java index eb932d29bf5b..ce5899abd7e6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/touch/TouchInsetManagerTest.java @@ -25,11 +25,11 @@ import static org.mockito.Mockito.when; import android.graphics.Rect; import android.graphics.Region; -import android.testing.AndroidTestingRunner; import android.view.AttachedSurfaceControl; 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; @@ -45,7 +45,7 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) public class TouchInsetManagerTest extends SysuiTestCase { @Mock private AttachedSurfaceControl mAttachedSurfaceControl; diff --git a/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt index bda339f7a022..eef4e3b5a747 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/tracing/TraceUtilsTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.tracing import android.os.Handler import android.os.Looper import android.os.Trace.TRACE_TAG_APP -import android.testing.AndroidTestingRunner import android.util.Log +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.app.tracing.TraceUtils.traceRunnable import com.android.app.tracing.namedRunnable @@ -30,7 +30,7 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @SmallTest class TraceUtilsTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java index 99f6303ad73d..25a44e3cc809 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/FakeSettingsTest.java @@ -83,7 +83,7 @@ public class FakeSettingsTest extends SysuiTestCase { @Test public void testRegisterContentObserver() { - mFakeSettings.registerContentObserver("cat", mContentObserver); + mFakeSettings.registerContentObserverSync("cat", mContentObserver); mFakeSettings.putString("cat", "hat"); @@ -93,7 +93,7 @@ public class FakeSettingsTest extends SysuiTestCase { @Test public void testRegisterContentObserverAllUsers() { - mFakeSettings.registerContentObserverForUser( + mFakeSettings.registerContentObserverForUserSync( mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL); mFakeSettings.putString("cat", "hat"); @@ -104,8 +104,8 @@ public class FakeSettingsTest extends SysuiTestCase { @Test public void testUnregisterContentObserver() { - mFakeSettings.registerContentObserver("cat", mContentObserver); - mFakeSettings.unregisterContentObserver(mContentObserver); + mFakeSettings.registerContentObserverSync("cat", mContentObserver); + mFakeSettings.unregisterContentObserverSync(mContentObserver); mFakeSettings.putString("cat", "hat"); @@ -115,9 +115,9 @@ public class FakeSettingsTest extends SysuiTestCase { @Test public void testUnregisterContentObserverAllUsers() { - mFakeSettings.registerContentObserverForUser( + mFakeSettings.registerContentObserverForUserSync( mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL); - mFakeSettings.unregisterContentObserver(mContentObserver); + mFakeSettings.unregisterContentObserverSync(mContentObserver); mFakeSettings.putString("cat", "hat"); @@ -128,7 +128,7 @@ public class FakeSettingsTest extends SysuiTestCase { @Test public void testContentObserverDispatchCorrectUser() { int user = 10; - mFakeSettings.registerContentObserverForUser( + mFakeSettings.registerContentObserverForUserSync( mFakeSettings.getUriFor("cat"), false, mContentObserver, UserHandle.USER_ALL ); diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt index ab95707046d9..eb11e383d519 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/SettingsProxyTest.kt @@ -52,14 +52,14 @@ class SettingsProxyTest : SysuiTestCase() { @Test fun registerContentObserver_inputString_success() { - mSettings.registerContentObserver(TEST_SETTING, mContentObserver) + mSettings.registerContentObserverSync(TEST_SETTING, mContentObserver) verify(mSettings.getContentResolver()) .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver)) } @Test fun registerContentObserver_inputString_notifyForDescendants_true() { - mSettings.registerContentObserver( + mSettings.registerContentObserverSync( TEST_SETTING, notifyForDescendants = true, mContentObserver @@ -70,14 +70,14 @@ class SettingsProxyTest : SysuiTestCase() { @Test fun registerContentObserver_inputUri_success() { - mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver) + mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver) verify(mSettings.getContentResolver()) .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver)) } @Test fun registerContentObserver_inputUri_notifyForDescendants_true() { - mSettings.registerContentObserver( + mSettings.registerContentObserverSync( TEST_SETTING_URI, notifyForDescendants = true, mContentObserver @@ -88,7 +88,7 @@ class SettingsProxyTest : SysuiTestCase() { @Test fun unregisterContentObserver() { - mSettings.unregisterContentObserver(mContentObserver) + mSettings.unregisterContentObserverSync(mContentObserver) verify(mSettings.getContentResolver()).unregisterContentObserver(eq(mContentObserver)) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt index 56328b933602..38469ee7955d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/util/settings/UserSettingsProxyTest.kt @@ -58,7 +58,7 @@ class UserSettingsProxyTest : SysuiTestCase() { @Test fun registerContentObserverForUser_inputString_success() { - mSettings.registerContentObserverForUser( + mSettings.registerContentObserverForUserSync( TEST_SETTING, mContentObserver, mUserTracker.userId @@ -74,7 +74,7 @@ class UserSettingsProxyTest : SysuiTestCase() { @Test fun registerContentObserverForUser_inputString_notifyForDescendants_true() { - mSettings.registerContentObserverForUser( + mSettings.registerContentObserverForUserSync( TEST_SETTING, notifyForDescendants = true, mContentObserver, @@ -91,7 +91,7 @@ class UserSettingsProxyTest : SysuiTestCase() { @Test fun registerContentObserverForUser_inputUri_success() { - mSettings.registerContentObserverForUser( + mSettings.registerContentObserverForUserSync( TEST_SETTING_URI, mContentObserver, mUserTracker.userId @@ -107,7 +107,7 @@ class UserSettingsProxyTest : SysuiTestCase() { @Test fun registerContentObserverForUser_inputUri_notifyForDescendants_true() { - mSettings.registerContentObserverForUser( + mSettings.registerContentObserverForUserSync( TEST_SETTING_URI, notifyForDescendants = true, mContentObserver, @@ -124,14 +124,14 @@ class UserSettingsProxyTest : SysuiTestCase() { @Test fun registerContentObserver_inputUri_success() { - mSettings.registerContentObserver(TEST_SETTING_URI, mContentObserver) + mSettings.registerContentObserverSync(TEST_SETTING_URI, mContentObserver) verify(mSettings.getContentResolver()) .registerContentObserver(eq(TEST_SETTING_URI), eq(false), eq(mContentObserver), eq(0)) } @Test fun registerContentObserver_inputUri_notifyForDescendants_true() { - mSettings.registerContentObserver( + mSettings.registerContentObserverSync( TEST_SETTING_URI, notifyForDescendants = true, mContentObserver diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java index 741b2e26e2aa..c81623e627ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/CsdWarningDialogTest.java @@ -27,9 +27,9 @@ import static org.mockito.Mockito.verify; import android.app.Notification; import android.app.NotificationManager; import android.media.AudioManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.messages.nano.SystemMessageProto; @@ -42,7 +42,7 @@ import org.junit.Test; import org.junit.runner.RunWith; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class CsdWarningDialogTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java index 69d7586e6ab4..f7371487a7c5 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java @@ -37,10 +37,10 @@ import android.media.IAudioService; import android.media.session.MediaSession; import android.os.Handler; import android.os.Process; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.accessibility.AccessibilityManager; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -65,7 +65,7 @@ import org.mockito.MockitoAnnotations; import java.util.concurrent.Executor; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @SmallTest @TestableLooper.RunWithLooper public class VolumeDialogControllerImplTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java index 9864439266b2..05d07e5256b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogImplTest.java @@ -52,7 +52,6 @@ import android.os.SystemClock; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Log; import android.view.Gravity; @@ -65,6 +64,7 @@ import android.widget.ImageButton; import android.widget.SeekBar; import androidx.test.core.view.MotionEventBuilder; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.jank.InteractionJankMonitor; @@ -107,7 +107,7 @@ import java.util.Arrays; import java.util.function.Predicate; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper(setAsMainLooper = true) public class VolumeDialogImplTest extends SysuiTestCase { VolumeDialogImpl mDialog; diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java index dc5597a1cce0..40094e507f9a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/QuickAccessWalletControllerTest.java @@ -33,9 +33,9 @@ import android.app.role.RoleManager; import android.content.Intent; import android.service.quickaccesswallet.GetWalletCardsRequest; import android.service.quickaccesswallet.QuickAccessWalletClient; -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; @@ -57,7 +57,7 @@ import org.mockito.MockitoAnnotations; import java.util.List; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class QuickAccessWalletControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt index d2387e83e3eb..6f99cd90ffc6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualLocationsServiceTest.kt @@ -6,6 +6,7 @@ import android.graphics.Bitmap import android.graphics.drawable.Icon import android.os.Looper import android.service.quickaccesswallet.WalletCard +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FakeFeatureFlags @@ -22,7 +23,6 @@ import kotlinx.coroutines.test.runTest 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.anySet import org.mockito.Mockito.doNothing @@ -31,7 +31,7 @@ import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) @SmallTest @kotlinx.coroutines.ExperimentalCoroutinesApi class WalletContextualLocationsServiceTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt index d5bdb59b5cda..4e44c4a224d4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/controller/WalletContextualSuggestionsControllerTest.kt @@ -22,6 +22,7 @@ import android.content.Intent import android.service.quickaccesswallet.GetWalletCardsResponse import android.service.quickaccesswallet.QuickAccessWalletClient import android.service.quickaccesswallet.WalletCard +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.BroadcastDispatcher @@ -42,7 +43,6 @@ import kotlinx.coroutines.test.runTest 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 @@ import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest -@RunWith(JUnit4::class) +@RunWith(AndroidJUnit4::class) class WalletContextualSuggestionsControllerTest : SysuiTestCase() { @Mock private lateinit var walletController: QuickAccessWalletController diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java index c1d11aa1eb89..38a61fecdc8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java @@ -42,9 +42,9 @@ import android.service.quickaccesswallet.GetWalletCardsResponse; import android.service.quickaccesswallet.QuickAccessWalletClient; import android.service.quickaccesswallet.QuickAccessWalletService; import android.service.quickaccesswallet.WalletCard; -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.UiEventLogger; @@ -68,7 +68,7 @@ import org.mockito.MockitoAnnotations; import java.util.Collections; import java.util.List; -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper @SmallTest public class WalletScreenControllerTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt index e46c1f554dd6..1df781f7179e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/util/WalletCardUtilsTest.kt @@ -17,8 +17,8 @@ package com.android.systemui.wallet.util import android.service.quickaccesswallet.WalletCard -import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.util.mockito.mock @@ -27,7 +27,7 @@ import org.junit.Test import org.junit.runner.RunWith /** Test class for WalletCardUtils */ -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @RunWithLooper @SmallTest class WalletCardUtilsTest : SysuiTestCase() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java index fc2030f694ad..6fb70de3569d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java @@ -44,13 +44,13 @@ import android.graphics.Bitmap; import android.graphics.ColorSpace; import android.graphics.Rect; import android.hardware.display.DisplayManager; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.Surface; import android.view.SurfaceHolder; import android.view.WindowManager; import android.view.WindowMetrics; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -65,7 +65,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class ImageWallpaperTest extends SysuiTestCase { private static final int LOW_BMP_WIDTH = 128; diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java index 33d09c1be696..75e027e3500d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/WallpaperLocalColorExtractorTest.java @@ -36,9 +36,9 @@ import android.graphics.Color; import android.graphics.Rect; import android.graphics.RectF; import android.platform.test.annotations.EnableFlags; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -56,7 +56,7 @@ import java.util.List; import java.util.concurrent.Executor; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @TestableLooper.RunWithLooper public class WallpaperLocalColorExtractorTest extends SysuiTestCase { private static final int LOW_BMP_WIDTH = 112; diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt index 78016840c3fb..2021f02e5a8a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt @@ -21,9 +21,9 @@ import android.content.SharedPreferences import android.content.pm.ShortcutInfo import android.content.res.Resources import android.os.UserHandle -import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.core.content.edit +import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.model.SysUiStateTest import com.android.wm.shell.bubbles.Bubble @@ -38,7 +38,7 @@ import org.junit.Test import org.junit.runner.RunWith @SmallTest -@RunWith(AndroidTestingRunner::class) +@RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class BubbleEducationControllerTest : SysUiStateTest() { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt index 9765d531472c..53285eb715ba 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeDisplayStateRepository.kt @@ -23,7 +23,6 @@ import com.android.systemui.dagger.SysUISingleton import dagger.Binds import dagger.Module import javax.inject.Inject -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -40,7 +39,7 @@ class FakeDisplayStateRepository @Inject constructor() : DisplayStateRepository override val currentDisplaySize: StateFlow<Size> = _currentDisplaySize.asStateFlow() private val _isLargeScreen = MutableStateFlow<Boolean>(false) - override val isLargeScreen: Flow<Boolean> = _isLargeScreen.asStateFlow() + override val isLargeScreen: StateFlow<Boolean> = _isLargeScreen.asStateFlow() override val isReverseDefaultRotation = false @@ -55,6 +54,10 @@ class FakeDisplayStateRepository @Inject constructor() : DisplayStateRepository fun setCurrentDisplaySize(size: Size) { _currentDisplaySize.value = size } + + fun setIsLargeScreen(isLargeScreen: Boolean) { + _isLargeScreen.value = isLargeScreen + } } @Module diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt index 7f9a71cd149e..56297f0d7f43 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorKosmos.kt @@ -25,6 +25,7 @@ import com.android.systemui.kosmos.Kosmos.Fixture val Kosmos.promptSelectorInteractor by Fixture { PromptSelectorInteractorImpl( fingerprintPropertyRepository = fingerprintPropertyRepository, + displayStateInteractor = displayStateInteractor, promptRepository = promptRepository, lockPatternUtils = lockPatternUtils ) 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 a05b5e65ce9d..ad5242e2e036 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 @@ package com.android.systemui.brightness.data.repository 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 22784e47d277..0e8427310895 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 @@ package com.android.systemui.brightness.domain.interactor 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/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt new file mode 100644 index 000000000000..d208465bd33d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/brightness/ui/viewmodel/BrightnessSliderViewModelKosmos.kt @@ -0,0 +1,31 @@ +/* + * 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.ui.viewmodel + +import com.android.systemui.brightness.domain.interactor.brightnessPolicyEnforcementInteractor +import com.android.systemui.brightness.domain.interactor.screenBrightnessInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope + +val Kosmos.brightnessSliderViewModel: BrightnessSliderViewModel by + Kosmos.Fixture { + BrightnessSliderViewModel( + screenBrightnessInteractor = screenBrightnessInteractor, + brightnessPolicyEnforcementInteractor = brightnessPolicyEnforcementInteractor, + applicationScope = applicationCoroutineScope, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt index cd2710ef8757..fb983f7c605f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorKosmos.kt @@ -21,6 +21,7 @@ import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.kosmos.testDispatcher import com.android.systemui.settings.userTracker import com.android.systemui.user.domain.interactor.selectedUserInteractor import com.android.systemui.util.mockito.mock @@ -29,6 +30,7 @@ val Kosmos.communalSettingsInteractor by Fixture { CommunalSettingsInteractor( bgScope = applicationCoroutineScope, bgExecutor = fakeExecutor, + bgDispatcher = testDispatcher, repository = communalSettingsRepository, userInteractor = selectedUserInteractor, userTracker = userTracker, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt index 1a45c4298691..22b8c8dbdfbd 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt @@ -104,7 +104,8 @@ class FakeKeyguardRepository @Inject constructor() : KeyguardRepository { private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel(BiometricUnlockMode.NONE, null)) - override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState + override val biometricUnlockState: StateFlow<BiometricUnlockModel> = + _biometricUnlockState.asStateFlow() private val _fingerprintSensorLocation = MutableStateFlow<Point?>(null) override val fingerprintSensorLocation: Flow<Point?> = _fingerprintSensorLocation diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractorKosmos.kt new file mode 100644 index 000000000000..7a3f925fccb6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BiometricUnlockInteractorKosmos.kt @@ -0,0 +1,27 @@ +/* + * 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.keyguard.domain.interactor + +import com.android.systemui.keyguard.data.repository.keyguardRepository +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture + +val Kosmos.biometricUnlockInteractor by Fixture { + BiometricUnlockInteractor( + keyguardRepository = keyguardRepository, + ) +} 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 b8620781bc1e..6d2d04a70538 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.data.repository.fakeConfigurationRepositor 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 @@ -49,6 +50,7 @@ import com.android.systemui.plugins.statusbar.statusBarStateController import com.android.systemui.power.data.repository.fakePowerRepository import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.domain.startable.scrimStartable import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.shared.model.sceneDataSource import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor @@ -58,6 +60,7 @@ import com.android.systemui.shade.shadeController import com.android.systemui.statusbar.chips.ui.viewmodel.ongoingActivityChipsViewModel import com.android.systemui.statusbar.notification.stack.domain.interactor.headsUpNotificationInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.sharedNotificationContainerInteractor +import com.android.systemui.statusbar.phone.scrimController import com.android.systemui.statusbar.pipeline.mobile.data.repository.fakeMobileConnectionsRepository import com.android.systemui.statusbar.policy.data.repository.fakeDeviceProvisioningRepository import com.android.systemui.statusbar.policy.domain.interactor.deviceProvisioningInteractor @@ -87,6 +90,7 @@ class KosmosJavaAdapter() { 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 } @@ -128,4 +132,6 @@ class KosmosJavaAdapter() { val shadeInteractor by lazy { kosmos.shadeInteractor } val ongoingActivityChipsViewModel by lazy { kosmos.ongoingActivityChipsViewModel } + val scrimController by lazy { kosmos.scrimController } + val scrimStartable by lazy { kosmos.scrimStartable } } 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 604c16fd9e74..5ff44e5d33c5 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 @@ var Kosmos.installedTilesRepository: InstalledTilesComponentRepository by 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 b870039982f1..d97a5b2bede2 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.external.tileLifecycleManagerFactory 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 @@ val Kosmos.currentTilesInteractor: CurrentTilesInteractor by installedTilesRepository, userRepository, minimumTilesRepository, + retailModeRepository, customTileStatePersister, { newQSTileFactory }, qsTileFactory, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/ScrimStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/ScrimStartableKosmos.kt new file mode 100644 index 000000000000..b64c84075936 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/ScrimStartableKosmos.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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.scene.domain.startable + +import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.keyguard.domain.interactor.biometricUnlockInteractor +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.scene.domain.interactor.sceneContainerOcclusionInteractor +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.settings.brightness.domain.interactor.brightnessMirrorShowingInteractor +import com.android.systemui.statusbar.phone.dozeServiceHost +import com.android.systemui.statusbar.phone.scrimController +import com.android.systemui.statusbar.phone.statusBarKeyguardViewManager +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.scrimStartable by Fixture { + ScrimStartable( + applicationScope = applicationCoroutineScope, + scrimController = scrimController, + sceneInteractor = sceneInteractor, + deviceEntryInteractor = deviceEntryInteractor, + keyguardInteractor = keyguardInteractor, + occlusionInteractor = sceneContainerOcclusionInteractor, + biometricUnlockInteractor = biometricUnlockInteractor, + statusBarKeyguardViewManager = statusBarKeyguardViewManager, + alternateBouncerInteractor = alternateBouncerInteractor, + brightnessMirrorShowingInteractor = brightnessMirrorShowingInteractor, + dozeServiceHost = dozeServiceHost, + ) +} 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 297d1d8c2c0a..0a3a2ee91773 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 @@ import com.android.systemui.util.mockito.mock val Kosmos.shadeLockscreenInteractor by Kosmos.Fixture { ShadeLockscreenInteractorImpl( - scope = testScope, + applicationScope = applicationCoroutineScope, + backgroundScope = applicationCoroutineScope, shadeInteractor = shadeInteractorImpl, sceneInteractor = sceneInteractor, lockIconViewController = mock(), diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt index 8c5ff1d5d216..c5625e47040a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ui/viewmodel/QuickSettingsShadeSceneViewModelKosmos.kt @@ -16,8 +16,12 @@ package com.android.systemui.shade.ui.viewmodel +import com.android.systemui.brightness.ui.viewmodel.brightnessSliderViewModel import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.qs.panels.ui.viewmodel.editModeViewModel +import com.android.systemui.qs.panels.ui.viewmodel.tileGridViewModel +import com.android.systemui.qs.ui.adapter.qsSceneAdapter import com.android.systemui.qs.ui.viewmodel.QuickSettingsShadeSceneViewModel val Kosmos.quickSettingsShadeSceneViewModel: QuickSettingsShadeSceneViewModel by @@ -25,5 +29,9 @@ val Kosmos.quickSettingsShadeSceneViewModel: QuickSettingsShadeSceneViewModel by QuickSettingsShadeSceneViewModel( applicationScope = applicationCoroutineScope, overlayShadeViewModel = overlayShadeViewModel, + brightnessSliderViewModel = brightnessSliderViewModel, + tileGridViewModel = tileGridViewModel, + editModeViewModel = editModeViewModel, + qsSceneAdapter = qsSceneAdapter, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt new file mode 100644 index 000000000000..569429f180df --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.statusbar.notification.stack + +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/util/settings/FakeGlobalSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java index beabaf5f5954..3a70cdfc42ed 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeGlobalSettings.java @@ -44,7 +44,7 @@ public class FakeGlobalSettings implements GlobalSettings { } @Override - public void registerContentObserver(Uri uri, boolean notifyDescendants, + public void registerContentObserverSync(Uri uri, boolean notifyDescendants, ContentObserver settingsObserver) { List<ContentObserver> observers; mContentObserversAllUsers.putIfAbsent(uri.toString(), new ArrayList<>()); @@ -53,7 +53,7 @@ public class FakeGlobalSettings implements GlobalSettings { } @Override - public void unregisterContentObserver(ContentObserver settingsObserver) { + public void unregisterContentObserverSync(ContentObserver settingsObserver) { for (Map.Entry<String, List<ContentObserver>> entry : mContentObserversAllUsers.entrySet()) { entry.getValue().remove(settingsObserver); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java index a49188687100..cd219ec127fc 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/settings/FakeSettings.java @@ -66,7 +66,7 @@ public class FakeSettings implements SecureSettings, SystemSettings { } @Override - public void registerContentObserverForUser(Uri uri, boolean notifyDescendants, + public void registerContentObserverForUserSync(Uri uri, boolean notifyDescendants, ContentObserver settingsObserver, int userHandle) { List<ContentObserver> observers; if (userHandle == UserHandle.USER_ALL) { @@ -81,7 +81,7 @@ public class FakeSettings implements SecureSettings, SystemSettings { } @Override - public void unregisterContentObserver(ContentObserver settingsObserver) { + public void unregisterContentObserverSync(ContentObserver settingsObserver) { for (SettingsKey key : mContentObservers.keySet()) { List<ContentObserver> observers = mContentObservers.get(key); observers.remove(settingsObserver); 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 3ac712918100..15ef26d58ece 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 @@ object TestAudioDevicesFactory { ) } - 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 3f51a790aade..e2d414e23abd 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 @@ import com.android.systemui.volume.mediaOutputInteractor 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 9c902cf57fde..680535dfa909 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 @@ class FakeLocalMediaRepositoryFactory(private val defaultProvider: () -> LocalMe 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 000000000000..9f11822adc0c --- /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 6d4576efd34c..2cd6ff2db457 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 android.content.applicationContext 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 @@ var Kosmos.mediaOutputViewModel by 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 004f37c16757..2c4bc7cb0d47 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 class CameraExtensionsProxyService extends Service { 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 c66443fb8a14..000000000000 --- 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 42f168bb4a6b..7342b0003ed5 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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + @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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } 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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ 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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ 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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ 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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ 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 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } } + 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 20b727cd6f09..d09cb3ee103a 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } /** + * 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub 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 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub //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 4cb3d247edb0..420bac759ea6 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 @@ public class ProxyAccessibilityServiceConnection extends AccessibilityServiceCon /** @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 0719ebaba707..7f4c808b2251 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 @@ public class MagnificationConnectionManager implements @ConnectionState private int mConnectionState = DISCONNECTED; + ConnectionStateChangedCallback mConnectionStateChangedCallback = null; private static final int WAIT_CONNECTION_TIMEOUT_MILLIS = 100; @@ -264,6 +265,9 @@ public class MagnificationConnectionManager implements } } } + if (mConnectionStateChangedCallback != null) { + mConnectionStateChangedCallback.onConnectionStateChanged(connection != null); + } } /** @@ -271,7 +275,7 @@ public class MagnificationConnectionManager implements */ public boolean isConnected() { synchronized (mLock) { - return mConnectionWrapper != null; + return mConnectionWrapper != null && mConnectionState == CONNECTED; } } @@ -1344,4 +1348,8 @@ public class MagnificationConnectionManager implements } } } + + 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 76367a2b11c3..9b7884711a6d 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 @@ public class MagnificationController implements MagnificationConnectionManager.C 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 ed8f1ab3a1b2..603683906d06 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 @@ public class MagnificationProcessor { 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/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 215f6402fa76..4a9900763a94 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_CLIPBOAR 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 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub 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 0fdf6d0fd507..f1339e91d3d4 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -232,7 +232,6 @@ java_library_static { "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 966478e33c73..a61925732256 100644 --- a/services/core/java/com/android/server/PackageWatchdog.java +++ b/services/core/java/com/android/server/PackageWatchdog.java @@ -138,12 +138,6 @@ public class PackageWatchdog { 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 @@ public class PackageWatchdog { @GuardedBy("mLock") private boolean mSyncRequired = false; - @GuardedBy("mLock") - private long mLastMitigation = -1000000; - @FunctionalInterface @VisibleForTesting interface SystemClock { @@ -409,16 +400,6 @@ public class PackageWatchdog { 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 @@ public class PackageWatchdog { 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/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 9be0e1ff464e..51da7f175bb9 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -499,8 +499,6 @@ public final class ActiveServices { private final ServiceAnrTimer mShortFGSAnrTimer; // ActivityManagerConstants.DEFAULT_SERVICE_START_FOREGROUND_TIMEOUT_MS private final ServiceAnrTimer mServiceFGAnrTimer; - // see ServiceRecord#getEarliestStopTypeAndTime() - private final ServiceAnrTimer mFGSAnrTimer; /** * Mapping of uid to {fgs_type, fgs_info} for time limited fgs types such as dataSync and @@ -784,9 +782,6 @@ public final class ActiveServices { this.mServiceFGAnrTimer = new ServiceAnrTimer(service, ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, "SERVICE_FOREGROUND_TIMEOUT"); - this.mFGSAnrTimer = new ServiceAnrTimer(service, - ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG, - "FGS_TIMEOUT"); } void systemServicesReady() { @@ -3811,8 +3806,9 @@ public final class ActiveServices { if (!sr.isFgsTimeLimited()) { // Reset timers since new type does not have a timeout. - mFGSAnrTimer.cancel(sr); mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr); + mAm.mHandler.removeMessages( + ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG, sr); return; } } @@ -3834,9 +3830,9 @@ public final class ActiveServices { } fgsTypeInfo.noteFgsFgsStart(nowUptime); - // We'll cancel the previous ANR timer and start a fresh one below. - mFGSAnrTimer.cancel(sr); + // We'll cancel the timeout and crash messages and post a fresh one below. mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr); + mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG, sr); final Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr); @@ -3864,8 +3860,8 @@ public final class ActiveServices { fgsTypeInfo.decNumParallelServices(); } Slog.d(TAG_SERVICE, "Stop FGS timeout: " + sr); - mFGSAnrTimer.cancel(sr); mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr); + mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG, sr); } void onUidRemovedLocked(int uid) { @@ -3892,7 +3888,8 @@ public final class ActiveServices { synchronized (mAm) { final int fgsType = getTimeLimitedFgsType(sr.foregroundServiceType); if (fgsType == ServiceInfo.FOREGROUND_SERVICE_TYPE_NONE || sr.app == null) { - mFGSAnrTimer.discard(sr); + mAm.mHandler.removeMessages( + ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG, sr); return; } @@ -3901,8 +3898,9 @@ public final class ActiveServices { final long nowUptime = SystemClock.uptimeMillis(); if (lastTopTime != Long.MIN_VALUE && constantTimeLimit > (nowUptime - lastTopTime)) { // Discard any other messages for this service - mFGSAnrTimer.discard(sr); mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_FGS_TIMEOUT_MSG, sr); + mAm.mHandler.removeMessages( + ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG, sr); // The app was in the TOP state after the FGS was started so its time allowance // should be counted from that time since this is considered a user interaction final Message msg = mAm.mHandler.obtainMessage( @@ -3913,7 +3911,6 @@ public final class ActiveServices { Slog.e(TAG_SERVICE, "FGS (" + ServiceInfo.foregroundServiceTypeToLabel(fgsType) + ") timed out: " + sr); - mFGSAnrTimer.accept(sr); traceInstant("FGS timed out: ", sr); final TimeLimitedFgsInfo fgsTypeInfo = getFgsTimeLimitedInfo(sr.appInfo.uid, fgsType); @@ -3940,7 +3937,9 @@ public final class ActiveServices { } // Crash the service after giving the service some time to clean up. - mFGSAnrTimer.start(sr, mAm.mConstants.mFgsCrashExtraWaitDuration); + final Message msg = mAm.mHandler.obtainMessage( + ActivityManagerService.SERVICE_FGS_CRASH_TIMEOUT_MSG, sr); + mAm.mHandler.sendMessageDelayed(msg, mAm.mConstants.mFgsCrashExtraWaitDuration); } } @@ -3963,8 +3962,9 @@ public final class ActiveServices { + ServiceInfo.foregroundServiceTypeToLabel(fgsType) + " did not stop within its timeout: " + sr.getComponentName(); - if (android.app.Flags.gateFgsTimeoutAnrBehavior()) { - // Log a WTF instead of throwing an ANR while the new behavior is gated. + if (android.app.Flags.gateFgsTimeoutAnrBehavior() + || !android.app.Flags.enableFgsTimeoutCrashBehavior()) { + // Log a WTF instead of crashing the app while the new behavior is gated. Slog.wtf(TAG, reason); return; } @@ -3981,23 +3981,6 @@ public final class ActiveServices { .createExtrasForService(sr.getComponentName())); } } - } else { - // ANR the app if the new crash behavior is not enabled - final TimeoutRecord tr = TimeoutRecord.forFgsTimeout(reason); - tr.mLatencyTracker.waitingOnAMSLockStarted(); - synchronized (mAm) { - tr.mLatencyTracker.waitingOnAMSLockEnded(); - - Slog.e(TAG_SERVICE, "FGS ANR'ed: " + sr); - traceInstant("FGS ANR: ", sr); - if (sr.app != null) { - mAm.appNotResponding(sr.app, tr); - } - - // TODO: Can we close the ANR dialog here, if it's still shown? Currently, the ANR - // dialog really doesn't remember the "cause" (especially if there have been - // multiple ANRs), so it's not doable. - } } } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index db4840dc76c5..211f952551d9 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.os.ProcLocksReader; 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.Map; 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 final class CachedAppOptimizer { 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 8647750d510f..ab34dd4477fd 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 @@ public class OomAdjuster { != 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 8d7a1c9f8228..8eef71e603b2 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_ALLOWED 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 final class PendingIntentRecord extends IIntentSender.Stub { }) 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 218434049869..b9cdf27a7415 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 @@ final class ServiceRecord extends Binder implements ComponentName.WithComponentN 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 1dc1846fbb96..1d21ccb62b8c 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.RemoteException; 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.Objects; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; - /** * @hide * (non final for mocking/spying) @@ -167,6 +169,15 @@ public class AudioDeviceBroker { @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 @@ public class AudioDeviceBroker { mDeviceInventory = new AudioDeviceInventory(this); mSystemServer = SystemServerAdapter.getDefaultAdapter(mContext); mAudioSystem = audioSystem; - + mScoManagedByAudio = scoManagedByAudio() + && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false); init(); } @@ -192,7 +204,8 @@ public class AudioDeviceBroker { mDeviceInventory = mockDeviceInventory; mSystemServer = mockSystemServer; mAudioSystem = audioSystem; - + mScoManagedByAudio = scoManagedByAudio() + && BluetoothProperties.isScoManagedByAudioEnabled().orElse(false); init(); } @@ -400,24 +413,24 @@ public class AudioDeviceBroker { 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 @@ public class AudioDeviceBroker { pw.println("\n" + prefix + "mAudioModeOwner: " + mAudioModeOwner); + pw.println("\n" + prefix + "mScoManagedByAudio: " + mScoManagedByAudio); + mBtHelper.dump(pw, prefix); } @@ -1837,10 +1852,10 @@ public class AudioDeviceBroker { ? 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 @@ public class AudioDeviceBroker { 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 @@ public class AudioDeviceBroker { 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 e0790da7cd09..287c92f86f0f 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 @@ public class AudioDeviceInventory { 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 02e80d611f3f..f652b33b3fd3 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 000000000000..5ea3c4bf538d --- /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 2addf6f9ec96..ef65b2523024 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.MODIFY_PHONE_STATE; 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.automaticBtDeviceType; 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_OFF; 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.os.SomeArgs; 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.Objects; 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 @@ public class AudioService extends IAudioService.Stub private final SettingsAdapter mSettings; private final AudioPolicyFacade mAudioPolicy; + private final AudioServerPermissionProvider mPermissionProvider; + private final MusicFxHelper mMusicFxHelper; /** Debug audio mode */ @@ -631,6 +644,17 @@ public class AudioService extends IAudioService.Stub // 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 class AudioService extends IAudioService.Stub 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(), - null); - + audioPolicyFacade, + null, + context.getSystemService(AppOpsManager.class), + PermissionEnforcer.fromContext(context), + audioserverPermissions() ? + initializeAudioServerPermissionProvider( + context, audioPolicyFacade, audioserverLifecycleExecutor) : + null + ); } @Override @@ -1092,25 +1124,6 @@ public class AudioService extends IAudioService.Stub /** * @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 class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub // 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 @@ public class AudioService extends IAudioService.Stub // 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 @@ public class AudioService extends IAudioService.Stub } 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub + 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 @@ public class AudioService extends IAudioService.Stub } } - 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub 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 @@ public class AudioService extends IAudioService.Stub } 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 @@ public class AudioService extends IAudioService.Stub && !isFullyMuted()) { index = 1; } + + if (DEBUG_VOL) { + Log.d(TAG, "setStreamVolumeIndexAS(" + mStreamType + ", " + index + ", " + device + + ")"); + } mAudioSystem.setStreamVolumeIndexAS(mStreamType, index, device); } @@ -8789,14 +8858,24 @@ public class AudioService extends IAudioService.Stub } 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 @@ public class AudioService extends IAudioService.Stub || 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 @@ public class AudioService extends IAudioService.Stub /*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 @@ public class AudioService extends IAudioService.Stub 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 7202fa286453..7f4bc74bd59e 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 @@ public class AudioSystemAdapter implements AudioSystem.RoutingUpdateCallback, } /** + * 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 07daecdfc9f6..991f94ba6f73 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -15,11 +15,7 @@ */ package com.android.server.audio; -import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_CARKIT; import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_DEFAULT; -import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_HEADSET; -import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_HEARING_AID; -import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_SPEAKER; import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_UNTETHERED_HEADSET; import static android.bluetooth.BluetoothDevice.DEVICE_TYPE_WATCH; import static android.media.AudioManager.AUDIO_DEVICE_CATEGORY_CARKIT; @@ -94,14 +90,14 @@ public class BtHelper { private final Map<BluetoothDevice, AudioDeviceAttributes> mResolvedScoAudioDevices = new HashMap<>(); - private @Nullable BluetoothHearingAid mHearingAid; + private @Nullable BluetoothHearingAid mHearingAid = null; - private @Nullable BluetoothLeAudio mLeAudio; + private @Nullable BluetoothLeAudio mLeAudio = null; private @Nullable BluetoothLeAudioCodecConfig mLeAudioCodecConfig; // Reference to BluetoothA2dp to query for AbsoluteVolume. - private @Nullable BluetoothA2dp mA2dp; + private @Nullable BluetoothA2dp mA2dp = null; private @Nullable BluetoothCodecConfig mA2dpCodecConfig; @@ -149,6 +145,14 @@ public class BtHelper { private static final int BT_LE_AUDIO_MIN_VOL = 0; private static final int BT_LE_AUDIO_MAX_VOL = 255; + // BtDevice constants currently rolling out under flag protection. Use own + // constants instead to avoid mainline dependency from flag library import + // TODO(b/335936458): remove once the BtDevice flag is rolled out + private static final String DEVICE_TYPE_SPEAKER = "Speaker"; + private static final String DEVICE_TYPE_HEADSET = "Headset"; + private static final String DEVICE_TYPE_CARKIT = "Carkit"; + private static final String DEVICE_TYPE_HEARING_AID = "HearingAid"; + /** * Returns a string representation of the scoAudioMode. */ @@ -401,50 +405,67 @@ public class BtHelper { 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 +475,6 @@ public class BtHelper { newIntent.putExtra(AudioManager.EXTRA_SCO_AUDIO_STATE, scoAudioState); sendStickyBroadcastToAll(newIntent); } - } /** * @@ -577,7 +597,11 @@ public class BtHelper { mHearingAid = null; break; case BluetoothProfile.LE_AUDIO: + if (mLeAudio != null && mLeAudioCallback != null) { + mLeAudio.unregisterCallback(mLeAudioCallback); + } mLeAudio = null; + mLeAudioCallback = null; mLeAudioCodecConfig = null; break; case BluetoothProfile.LE_AUDIO_BROADCAST: @@ -596,8 +620,6 @@ public class BtHelper { // BluetoothLeAudio callback used to update the list of addresses in the same group as a // connected LE Audio device - MyLeAudioCallback mLeAudioCallback = null; - class MyLeAudioCallback implements BluetoothLeAudio.Callback { @Override public void onCodecConfigChanged(int groupId, @@ -620,6 +642,8 @@ public class BtHelper { } } + MyLeAudioCallback mLeAudioCallback = null; + // @GuardedBy("mDeviceBroker.mSetModeLock") @GuardedBy("AudioDeviceBroker.this.mDeviceStateLock") /*package*/ synchronized void onBtProfileConnected(int profile, BluetoothProfile proxy) { @@ -635,18 +659,28 @@ public class BtHelper { onHeadsetProfileConnected((BluetoothHeadset) proxy); return; case BluetoothProfile.A2DP: + if (((BluetoothA2dp) proxy).equals(mA2dp)) { + return; + } mA2dp = (BluetoothA2dp) proxy; break; case BluetoothProfile.HEARING_AID: + if (((BluetoothHearingAid) proxy).equals(mHearingAid)) { + return; + } mHearingAid = (BluetoothHearingAid) proxy; break; case BluetoothProfile.LE_AUDIO: - if (mLeAudio == null) { - mLeAudioCallback = new MyLeAudioCallback(); - ((BluetoothLeAudio) proxy).registerCallback( - mContext.getMainExecutor(), mLeAudioCallback); + if (((BluetoothLeAudio) proxy).equals(mLeAudio)) { + return; + } + if (mLeAudio != null && mLeAudioCallback != null) { + mLeAudio.unregisterCallback(mLeAudioCallback); } mLeAudio = (BluetoothLeAudio) proxy; + mLeAudioCallback = new MyLeAudioCallback(); + mLeAudio.registerCallback( + mContext.getMainExecutor(), mLeAudioCallback); break; case BluetoothProfile.A2DP_SINK: case BluetoothProfile.LE_AUDIO_BROADCAST: diff --git a/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java b/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java index 37b812685a3d..09701e49a8ac 100644 --- a/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java +++ b/services/core/java/com/android/server/audio/DefaultAudioPolicyFacade.java @@ -16,100 +16,68 @@ package com.android.server.audio; -import android.annotation.NonNull; import android.annotation.Nullable; import android.media.IAudioPolicyService; -import android.media.permission.ClearCallingIdentityContext; -import android.media.permission.SafeCloseable; +import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; -import android.os.ServiceManager; -import android.util.Log; -import com.android.internal.annotations.GuardedBy; +import com.android.media.permission.INativePermissionController; + +import java.util.Objects; +import java.util.concurrent.Executor; +import java.util.function.Function; /** - * Default implementation of a facade to IAudioPolicyManager which fulfills AudioService - * dependencies. This forwards calls as-is to IAudioPolicyManager. - * Public methods throw IllegalStateException if AudioPolicy is not initialized/available + * Default implementation of a facade to IAudioPolicyService which fulfills AudioService + * dependencies. This forwards calls as-is to IAudioPolicyService. */ -public class DefaultAudioPolicyFacade implements AudioPolicyFacade, IBinder.DeathRecipient { +public class DefaultAudioPolicyFacade implements AudioPolicyFacade { - private static final String TAG = "DefaultAudioPolicyFacade"; private static final String AUDIO_POLICY_SERVICE_NAME = "media.audio_policy"; - private final Object mServiceLock = new Object(); - @GuardedBy("mServiceLock") - private IAudioPolicyService mAudioPolicy; + private final ServiceHolder<IAudioPolicyService> mServiceHolder; - public DefaultAudioPolicyFacade() { - try { - getAudioPolicyOrInit(); - } catch (IllegalStateException e) { - // Log and suppress this exception, we may be able to connect later - Log.e(TAG, "Failed to initialize APM connection", e); - } + /** + * @param e - Executor for service start tasks + */ + public DefaultAudioPolicyFacade(Executor e) { + mServiceHolder = + new ServiceHolder( + AUDIO_POLICY_SERVICE_NAME, + (Function<IBinder, IAudioPolicyService>) + IAudioPolicyService.Stub::asInterface, + e); + mServiceHolder.registerOnStartTask(i -> Binder.allowBlocking(i.asBinder())); } @Override public boolean isHotwordStreamSupported(boolean lookbackAudio) { - IAudioPolicyService ap = getAudioPolicyOrInit(); - try (SafeCloseable ignored = ClearCallingIdentityContext.create()) { + IAudioPolicyService ap = mServiceHolder.waitForService(); + try { return ap.isHotwordStreamSupported(lookbackAudio); } catch (RemoteException e) { - resetServiceConnection(ap.asBinder()); - throw new IllegalStateException(e); + mServiceHolder.attemptClear(ap.asBinder()); + throw new IllegalStateException(); } } @Override - public void binderDied() { - Log.wtf(TAG, "Unexpected binderDied without IBinder object"); - } - - @Override - public void binderDied(@NonNull IBinder who) { - resetServiceConnection(who); - } - - private void resetServiceConnection(@Nullable IBinder deadAudioPolicy) { - synchronized (mServiceLock) { - if (mAudioPolicy != null && mAudioPolicy.asBinder().equals(deadAudioPolicy)) { - mAudioPolicy.asBinder().unlinkToDeath(this, 0); - mAudioPolicy = null; - } - } - } - - private @Nullable IAudioPolicyService getAudioPolicy() { - synchronized (mServiceLock) { - return mAudioPolicy; + 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; } } - /* - * Does not block. - * @throws IllegalStateException for any failed connection - */ - private @NonNull IAudioPolicyService getAudioPolicyOrInit() { - synchronized (mServiceLock) { - if (mAudioPolicy != null) { - return mAudioPolicy; - } - // Do not block while attempting to connect to APM. Defer to caller. - IAudioPolicyService ap = IAudioPolicyService.Stub.asInterface( - ServiceManager.checkService(AUDIO_POLICY_SERVICE_NAME)); - if (ap == null) { - throw new IllegalStateException(TAG + ": Unable to connect to AudioPolicy"); - } - try { - ap.asBinder().linkToDeath(this, 0); - } catch (RemoteException e) { - throw new IllegalStateException( - TAG + ": Unable to link deathListener to AudioPolicy", e); - } - mAudioPolicy = ap; - return mAudioPolicy; - } + @Override + public void registerOnStartTask(Runnable task) { + mServiceHolder.registerOnStartTask(unused -> task.run()); } } diff --git a/services/core/java/com/android/server/audio/ServiceHolder.java b/services/core/java/com/android/server/audio/ServiceHolder.java new file mode 100644 index 000000000000..e2588fb4fdb1 --- /dev/null +++ b/services/core/java/com/android/server/audio/ServiceHolder.java @@ -0,0 +1,219 @@ +/* + * 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 android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.IInterface; +import android.os.IServiceCallback; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.Log; + +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; + +/** + * Manages a remote service which can start and stop. Allows clients to add tasks to run when the + * remote service starts or dies. + * + * <p>Example usage should look something like: + * + * <pre> + * var service = mServiceHolder.checkService(); + * if (service == null) handleFailure(); + * try { + * service.foo(); + * } catch (RemoteException e) { + * mServiceHolder.attemptClear(service.asBinder()); + * handleFailure(); + * } + * </pre> + */ +public class ServiceHolder<I extends IInterface> implements IBinder.DeathRecipient { + + private final String mTag; + private final String mServiceName; + private final Function<? super IBinder, ? extends I> mCastFunction; + private final Executor mExecutor; + private final ServiceProviderFacade mServiceProvider; + + private final AtomicReference<I> mService = new AtomicReference(); + private final Set<Consumer<I>> mOnStartTasks = ConcurrentHashMap.newKeySet(); + private final Set<Consumer<I>> mOnDeathTasks = ConcurrentHashMap.newKeySet(); + + private final IServiceCallback mServiceListener = + new IServiceCallback.Stub() { + @Override + public void onRegistration(String name, IBinder binder) { + onServiceInited(binder); + } + }; + + // For test purposes + public static interface ServiceProviderFacade { + public void registerForNotifications(String name, IServiceCallback listener); + + public IBinder checkService(String name); + + public IBinder waitForService(String name); + } + + public ServiceHolder( + @NonNull String serviceName, + @NonNull Function<? super IBinder, ? extends I> castFunction, + @NonNull Executor executor) { + this( + serviceName, + castFunction, + executor, + new ServiceProviderFacade() { + @Override + public void registerForNotifications(String name, IServiceCallback listener) { + try { + ServiceManager.registerForNotifications(name, listener); + } catch (RemoteException e) { + throw new IllegalStateException("ServiceManager died!!", e); + } + } + + @Override + public IBinder checkService(String name) { + return ServiceManager.checkService(name); + } + + @Override + public IBinder waitForService(String name) { + return ServiceManager.waitForService(name); + } + }); + } + + public ServiceHolder( + @NonNull String serviceName, + @NonNull Function<? super IBinder, ? extends I> castFunction, + @NonNull Executor executor, + @NonNull ServiceProviderFacade provider) { + mServiceName = Objects.requireNonNull(serviceName); + mCastFunction = Objects.requireNonNull(castFunction); + mExecutor = Objects.requireNonNull(executor); + mServiceProvider = Objects.requireNonNull(provider); + mTag = "ServiceHolder: " + serviceName; + mServiceProvider.registerForNotifications(mServiceName, mServiceListener); + } + + /** + * Add tasks to run when service becomes available. Ran on the executor provided at + * construction. Note, for convenience, if the service is already connected, the task is + * immediately run. + */ + public void registerOnStartTask(Consumer<I> task) { + mOnStartTasks.add(task); + I i; + if ((i = mService.get()) != null) { + mExecutor.execute(() -> task.accept(i)); + } + } + + public void unregisterOnStartTask(Consumer<I> task) { + mOnStartTasks.remove(task); + } + + /** + * Add tasks to run when service goes down. Ran on the executor provided at construction. Should + * be called before getService to avoid dropping a death notification. + */ + public void registerOnDeathTask(Consumer<I> task) { + mOnDeathTasks.add(task); + } + + public void unregisterOnDeathTask(Consumer<I> task) { + mOnDeathTasks.remove(task); + } + + @Override + public void binderDied(@NonNull IBinder who) { + attemptClear(who); + } + + @Override + public void binderDied() { + throw new AssertionError("Wrong binderDied called, this should never happen"); + } + + /** + * Notify the holder that the service has gone done, usually in response to a RemoteException. + * Equivalent to receiving a binder death notification. + */ + public void attemptClear(IBinder who) { + // Possibly prone to weird races, resulting in spurious dead/revive, + // but that should be fine. + var current = mService.get(); + if (current != null + && Objects.equals(current.asBinder(), who) + && mService.compareAndSet(current, null)) { + who.unlinkToDeath(this, 0); + for (var r : mOnDeathTasks) { + mExecutor.execute(() -> r.accept(current)); + } + } + } + + /** Get the service, without blocking. Can trigger start tasks, on the provided executor. */ + public @Nullable I checkService() { + var s = mService.get(); + if (s != null) return s; + IBinder registered = mServiceProvider.checkService(mServiceName); + if (registered == null) return null; + return onServiceInited(registered); + } + + /** Get the service, but block. Can trigger start tasks, on the provided executor. */ + public @NonNull I waitForService() { + var s = mService.get(); + return (s != null) ? s : onServiceInited(mServiceProvider.waitForService(mServiceName)); + } + + /* + * Called when the native service is initialized. + */ + private @NonNull I onServiceInited(@NonNull IBinder who) { + var service = mCastFunction.apply(who); + Objects.requireNonNull(service); + if (!mService.compareAndSet(null, service)) { + return service; + } + // Even if the service has immediately died, we should perform these tasks for consistency + for (var r : mOnStartTasks) { + mExecutor.execute(() -> r.accept(service)); + } + try { + who.linkToDeath(this, 0); + } catch (RemoteException e) { + Log.e(mTag, "Immediate service death. Service crash-looping"); + attemptClear(who); + } + // This interface is non-null, but could represent a dead object + return service; + } +} diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig index 712dcee55b7b..92fd9cbcf14e 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 @@ flag { 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 5690a9e6ec89..69bc66fea56c 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 @@ abstract class DisplayAdapter { 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 baa154d6d7b9..184ae41fe045 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 @@ public final class DisplayBrightnessState { 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 @@ public final class DisplayBrightnessState { mShouldUpdateScreenBrightnessSetting = builder.shouldUpdateScreenBrightnessSetting(); mBrightnessEvent = builder.getBrightnessEvent(); mBrightnessAdjustmentFlag = builder.getBrightnessAdjustmentFlag(); + mIsUserInitiatedChange = builder.isUserInitiatedChange(); } /** @@ -148,6 +151,13 @@ public final class DisplayBrightnessState { 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 @@ public final class DisplayBrightnessState { 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 @@ public final class DisplayBrightnessState { && mShouldUpdateScreenBrightnessSetting == otherState.shouldUpdateScreenBrightnessSetting() && Objects.equals(mBrightnessEvent, otherState.getBrightnessEvent()) - && mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag(); + && mBrightnessAdjustmentFlag == otherState.getBrightnessAdjustmentFlag() + && mIsUserInitiatedChange == otherState.isUserInitiatedChange(); } @Override @@ -207,7 +219,8 @@ public final class DisplayBrightnessState { return Objects.hash(mBrightness, mSdrBrightness, mBrightnessReason, mShouldUseAutoBrightness, mIsSlowChange, mMaxBrightness, mMinBrightness, mCustomAnimationRate, - mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag); + mShouldUpdateScreenBrightnessSetting, mBrightnessEvent, mBrightnessAdjustmentFlag, + mIsUserInitiatedChange); } /** @@ -236,6 +249,8 @@ public final class DisplayBrightnessState { public int mBrightnessAdjustmentFlag = 0; + private boolean mIsUserInitiatedChange; + /** * Create a builder starting with the values from the specified {@link * DisplayBrightnessState}. @@ -257,6 +272,7 @@ public final class DisplayBrightnessState { state.shouldUpdateScreenBrightnessSetting()); builder.setBrightnessEvent(state.getBrightnessEvent()); builder.setBrightnessAdjustmentFlag(state.getBrightnessAdjustmentFlag()); + builder.setIsUserInitiatedChange(state.isUserInitiatedChange()); return builder; } @@ -464,5 +480,20 @@ public final class DisplayBrightnessState { 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 d4c0b0180242..5fd025399b12 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_FULL; 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 @@ public final class DisplayManagerService extends SystemService { } 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 b97053b21b8e..8d71c701cf87 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 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call // request changes. final boolean wasShortTermModelActive = mAutomaticBrightnessStrategy.isShortTermModelActive(); + boolean userInitiatedChange = displayBrightnessState.isUserInitiatedChange(); boolean allowAutoBrightnessWhileDozing = mDisplayBrightnessController.isAllowAutoBrightnessWhileDozingConfig(); if (mFlags.offloadControlsDozeAutoBrightness() && mFlags.isDisplayOffloadEnabled() @@ -1413,13 +1414,14 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call 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 182b05a68028..44846f310348 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 @@ final class LocalDisplayAdapter extends DisplayAdapter { } 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 aa17df6562d8..d567331a59ce 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 @@ public final class DisplayBrightnessController { 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 82c0bbfde47e..304640b884ef 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 @@ public final class StrategyExecutionRequest { 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 @@ public final class StrategyExecutionRequest { return mCurrentScreenBrightness; } + public boolean isUserSetBrightnessChanged() { + return mUserSetBrightnessChanged; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof StrategyExecutionRequest)) { @@ -50,11 +58,13 @@ public final class StrategyExecutionRequest { } 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 29073644ee2e..2b5241fd5baf 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 @@ public class AutomaticBrightnessStrategy extends AutomaticBrightnessStrategy2 .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 3463649aa000..0b92317a61cb 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 @@ public class FallbackBrightnessStrategy implements DisplayBrightnessStrategy{ // 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 f923cbc978ff..f56d8036f870 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 @@ public class DisplayManagerFlags { } /** - * @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 76a2827050a9..d519748929bf 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 @@ public class DisplayModeDirector { * 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,104 +1265,84 @@ public class DisplayModeDirector { */ 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); } } - - synchronized (mLock) { - setAppRequestedModeLocked(displayId, modeId); - setAppPreferredRefreshRateRangeLocked(displayId, requestedMinRefreshRateRange, - requestedMaxRefreshRateRange); - } + return mode; } - @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); - } - for (int i = 0; i < modes.length; i++) { - if (modes[i].matches(defaultMode.getPhysicalWidth(), - defaultMode.getPhysicalHeight(), refreshRate)) { - return modes[i]; + 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 null; + return refreshRateRange != null + ? Vote.forRenderFrameRates(refreshRateRange.min, refreshRateRange.max) : 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()); + 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 { - baseModeRefreshRateVote = - Vote.forBaseModeRefreshRate(requestedMode.getRefreshRate()); + vote = Vote.forBaseModeRefreshRate(mode.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); + } else if (requestedRefreshRate != 0f && mIgnorePreferredRefreshRate) { + vote = Vote.forRequestedRefreshRate(requestedRefreshRate); + } // !mIgnorePreferredRefreshRate case is handled by findModeLocked + return vote; } - 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; + @Nullable + 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)) { + return modes[i]; } } - - 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); + return null; } private Display.Mode findAppModeByIdLocked(int displayId, int modeId) { @@ -1384,19 +1360,7 @@ public class DisplayModeDirector { 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 000000000000..843cf8889a74 --- /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 5b6bbc512a42..a83b9395dfc0 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 @@ import java.util.Map; */ 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 @@ public class SyntheticModeManager { 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 3e8b3c5ec7ca..1ec469c21f7b 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 @@ interface Vote { 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 d4ce892eeba9..00a922630d8e 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.Display; 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 @@ final class VoteSummary { 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 @@ final class VoteSummary { } 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 @@ final class VoteSummary { minHeight = 0; disableRefreshRateSwitching = false; appRequestBaseModeRefreshRate = 0f; + requestedRefreshRates.clear(); supportedRefreshRates = null; supportedModeIds = null; if (mLoggingEnabled) { @@ -393,6 +418,7 @@ final class VoteSummary { + ", 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 dbd1e65e8b0f..6e027c6d44c4 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 @@ public class HdmiControlService extends SystemService { /** 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 dd6433d98553..82ecb4acb197 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.internal.inputmethod.DirectBootAwareness; 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 @@ final class AdditionalSubtypeMapRepository { @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 @@ final class AdditionalSubtypeMapRepository { 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 @@ final class AdditionalSubtypeMapRepository { @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 @@ final class AdditionalSubtypeMapRepository { @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 79f1a9c90f53..c19cb030ef37 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.ArrayList; 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 @@ final class HandwritingModeController { private boolean mDelegationConnectionlessFlow; private Runnable mDelegationIdleTimeoutRunnable; private Handler mDelegationIdleTimeoutHandler; - private IntConsumer mPointerToolTypeConsumer; private final Runnable mDiscardDelegationTextRunnable; private HandwritingEventReceiverSurface mHandwritingSurface; @@ -99,7 +97,7 @@ final class HandwritingModeController { @AnyThread HandwritingModeController(Context context, Looper uiThreadLooper, - Runnable inkWindowInitRunnable, IntConsumer toolTypeConsumer, + Runnable inkWindowInitRunnable, Runnable discardDelegationTextRunnable) { mContext = context; mLooper = uiThreadLooper; @@ -109,7 +107,6 @@ final class HandwritingModeController { mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); mCurrentRequestId = 0; mInkWindowInitRunnable = inkWindowInitRunnable; - mPointerToolTypeConsumer = toolTypeConsumer; mDiscardDelegationTextRunnable = discardDelegationTextRunnable; } @@ -389,16 +386,10 @@ final class HandwritingModeController { "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 dace67f2c462..f61ca61c1e04 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.InlineSuggestionsRequestInfo; 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 @@ import java.util.List; */ 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 @@ public abstract class InputMethodManagerInternal { * * @param interactive the interactive mode parameter */ + @ImfLockFree public abstract void setInteractive(boolean interactive); /** @@ -61,6 +77,7 @@ public abstract class InputMethodManagerInternal { * @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 @@ public abstract class InputMethodManagerInternal { * * @param listener the listener to add */ + @ImfLockFree public abstract void registerInputMethodListListener(InputMethodListListener listener); /** @@ -178,6 +196,7 @@ public abstract class InputMethodManagerInternal { * * @param displayId the display hosting the IME window */ + @ImfLockFree public abstract void removeImeSurface(int displayId); /** @@ -188,12 +207,14 @@ public abstract class InputMethodManagerInternal { * @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 @@ public abstract class InputMethodManagerInternal { */ 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 @@ public abstract class InputMethodManagerInternal { int deviceId, @Nullable String imeId) { } + @ImfLockFree @Override public void registerInputMethodListListener(InputMethodListListener listener) { } @@ -300,10 +324,12 @@ public abstract class InputMethodManagerInternal { 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 @@ public abstract class InputMethodManagerInternal { @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 5843d72f346a..76f441cc1c9b 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.CopyOnWriteArrayList; 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; /** @@ -1162,7 +1161,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. synchronized (ImfLock.class) { mService.mUserDataRepository.getOrCreate(userId); if (mService.mExperimentalConcurrentMultiUserModeEnabled) { - if (mService.mCurrentUserId != userId) { + if (mService.mCurrentUserId != userId && mService.mSystemReady) { mService.experimentalInitializeVisibleBackgroundUserLocked(userId); } } @@ -1305,12 +1304,9 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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,18 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( getPackageManagerForUser(mContext, currentUserId), newSettings.getEnabledInputMethodList()); + + final var unused = SystemServerInitThreadPool.submit( + AdditionalSubtypeMapRepository::startWriterThread, + "Start AdditionalSubtypeMapRepository's writer thread"); + + if (mExperimentalConcurrentMultiUserModeEnabled) { + for (int userId : mUserManagerInternal.getUserIds()) { + if (userId != mCurrentUserId) { + experimentalInitializeVisibleBackgroundUserLocked(userId); + } + } + } } } } @@ -2884,10 +2892,22 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. */ @GuardedBy("ImfLock.class") void experimentalInitializeVisibleBackgroundUserLocked(@UserIdInt int userId) { - if (!mUserManagerInternal.isUserVisible(userId)) { - return; - } final var settings = InputMethodSettingsRepository.get(userId); + + // Until we figure out what makes most sense, we enable all the pre-installed IMEs in + // concurrent multi-user IME mode. + String enabledImeIdsStr = settings.getEnabledInputMethodsStr(); + for (var imi : settings.getMethodList()) { + if (!imi.isSystem()) { + return; + } + enabledImeIdsStr = InputMethodUtils.concatEnabledImeIds(enabledImeIdsStr, imi.getId()); + } + if (!TextUtils.equals(settings.getEnabledInputMethodsStr(), enabledImeIdsStr)) { + settings.putEnabledInputMethodsStr(enabledImeIdsStr); + } + + // Also update the currently-selected IME. String id = settings.getSelectedInputMethod(); if (TextUtils.isEmpty(id)) { final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME( @@ -3412,8 +3432,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 +3449,29 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 +4326,10 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. || 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 +5520,14 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. 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 +5613,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } + @ImfLockFree @Override public void registerInputMethodListListener(InputMethodListListener listener) { mInputMethodListListeners.addIfAbsent(listener); @@ -5609,11 +5661,13 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } + @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 +5745,7 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. } } + @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 1c958a929546..23f947cc8452 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 @@ final class InputMethodSubtypeSwitchingController { } 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 563f93e96331..b9e09605477a 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 @@ class LocaleManagerBackupHelper { * 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 @@ class LocaleManagerBackupHelper { 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 @@ class LocaleManagerBackupHelper { } synchronized (mStagedDataLock) { - cleanStagedDataForOldEntriesLocked(); + cleanStagedDataForOldEntriesLocked(userId); } HashMap<String, LocalesInfo> pkgStates = new HashMap<>(); @@ -207,14 +209,11 @@ class LocaleManagerBackupHelper { 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 @@ class LocaleManagerBackupHelper { // 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 @@ class LocaleManagerBackupHelper { } } - 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 @@ class LocaleManagerBackupHelper { * 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 @@ class LocaleManagerBackupHelper { */ 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 @@ class LocaleManagerBackupHelper { } } - 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 @@ class LocaleManagerBackupHelper { } } - 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 @@ class LocaleManagerBackupHelper { 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 @@ class LocaleManagerBackupHelper { synchronized (mStagedDataLock) { deleteStagedDataLocked(userId); removeProfileFromPersistedInfo(userId); + removeArchivedPackagesForUser(userId); } } } catch (Exception e) { @@ -533,29 +561,162 @@ class LocaleManagerBackupHelper { return; } - StagedData stagedData = mStagedData.get(userId); - for (String pkgName : stagedData.mPackageStates.keySet()) { - LocalesInfo localesInfo = stagedData.mPackageStates.get(pkgName); + 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); - if (pkgName.equals(packageName)) { + // Remove the restored entry from the staged data list. + if (!sp.edit().remove(packageName).commit()) { + Slog.e(TAG, "Failed to commit data!"); + } + } - checkExistingLocalesAndApplyRestore(pkgName, localesInfo, userId); + // 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); + } + } - // Remove the restored entry from the staged data list. - stagedData.mPackageStates.remove(pkgName); + private File getStagedDataFile(@UserIdInt int userId) { + return mStagedDataFiles == null ? new File(Environment.getDataSystemDeDirectory(userId), + LOCALES_STAGED_DATA_PREFS) : mStagedDataFiles.get(userId); + } - // Remove the stage data entry for user if there are no more packages to restore. - if (stagedData.mPackageStates.isEmpty()) { - mStagedData.remove(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); + } - // No need to loop further after restoring locales because the staged data will - // contain at most one entry for the newly added package. - break; + /** + * 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(); + } + } + SharedPreferences createPersistedInfo() { final File prefsFile = new File( Environment.getDataSystemDeDirectory(UserHandle.USER_SYSTEM), diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index b3ab229927fe..ae3d36acdb7f 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 class LockSettingsService extends ILockSettings.Stub { 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 e1cd2c585146..d0b8990e37c4 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.annotations.VisibleForTesting; 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 @@ class RebootEscrowManager { ERROR_KEYSTORE_FAILURE, ERROR_NO_NETWORK, ERROR_TIMEOUT_EXHAUSTED, + ERROR_NO_REBOOT_ESCROW_DATA, }) @Retention(RetentionPolicy.SOURCE) @interface RebootEscrowErrorCode { @@ -153,6 +158,7 @@ class RebootEscrowManager { 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 @@ class RebootEscrowManager { 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 @@ class RebootEscrowManager { return (UserManager) mContext.getSystemService(Context.USER_SERVICE); } + public UserManagerInternal getUserManagerInternal() { + return mUserManagerInternal; + } + public RebootEscrowKeyStoreManager getKeyStoreManager() { return mKeyStoreManager; } @@ -402,8 +417,8 @@ class RebootEscrowManager { } 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 @@ class RebootEscrowManager { onEscrowRestoreComplete(false, attemptCount, retryHandler); } - void loadRebootEscrowDataIfAvailable(Handler retryHandler) { - List<UserInfo> users = mUserManager.getUsers(); + 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)) { + Slog.d(TAG, "No reboot escrow data found for user " + user); + noEscrowDataUsers.add(user.id); + } + } + List<UserInfo> rebootEscrowUsers = new ArrayList<>(); for (UserInfo user : users) { - if (mCallbacks.isUserSecure(user.id) && mStorage.hasRebootEscrow(user.id)) { - rebootEscrowUsers.add(user); + // 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 363684f618cc..09605fefe80e 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 @@ abstract class MediaRoute2Provider { 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 @@ abstract class MediaRoute2Provider { 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 @@ abstract class MediaRoute2Provider { */ 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 @@ abstract class MediaRoute2Provider { 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 386657e99e36..71cbcb91100f 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 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider 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 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider 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 76930a003e46..6b409ee6f482 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { mPendingSessionCreationOrTransferRequest = new SessionCreationOrTransferRequest( requestId, - routeId, + routeOriginalId, RoutingSessionInfo.TRANSFER_REASON_FALLBACK, transferInitiatorUserHandle, transferInitiatorPackageName); @@ -204,7 +204,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { transferInitiatorUserHandle, transferInitiatorPackageName, SYSTEM_SESSION_ID, - routeId, + routeOriginalId, transferReason); } @@ -234,15 +234,15 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { // 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } @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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { } 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 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { Slog.w( TAG, "Session creation failed to route " - + mPendingSessionCreationOrTransferRequest.mTargetRouteId); + + mPendingSessionCreationOrTransferRequest + .mTargetOriginalRouteId); } mPendingSessionCreationOrTransferRequest = null; mCallback.onRequestFailed( @@ -529,7 +531,7 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { Slog.w( TAG, "Session creation waiting state to route " - + mPendingSessionCreationOrTransferRequest.mTargetRouteId); + + mPendingSessionCreationOrTransferRequest.mTargetOriginalRouteId); } } } @@ -541,7 +543,8 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { // 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/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java index 27b85746fec2..d060f8f2d036 100644 --- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java +++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java @@ -32,12 +32,13 @@ import android.service.notification.DeviceEffectsApplier; import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenModeConfig; import android.service.notification.ZenModeConfig.ConfigChangeOrigin; +import android.util.Slog; import com.android.internal.annotations.GuardedBy; /** Default implementation for {@link DeviceEffectsApplier}. */ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { - + private static final String TAG = "DeviceEffectsApplier"; private static final String SUPPRESS_AMBIENT_DISPLAY_TOKEN = "DefaultDeviceEffectsApplier:SuppressAmbientDisplay"; private static final int SATURATION_LEVEL_GRAYSCALE = 0; @@ -75,28 +76,44 @@ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { Binder.withCleanCallingIdentity(() -> { if (mLastAppliedEffects.shouldSuppressAmbientDisplay() != effects.shouldSuppressAmbientDisplay()) { - mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN, - effects.shouldSuppressAmbientDisplay()); + try { + mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN, + effects.shouldSuppressAmbientDisplay()); + } catch (Exception e) { + Slog.e(TAG, "Could not change AOD override", e); + } } if (mLastAppliedEffects.shouldDisplayGrayscale() != effects.shouldDisplayGrayscale()) { if (mColorDisplayManager != null) { - mColorDisplayManager.setSaturationLevel( - effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE - : SATURATION_LEVEL_FULL_COLOR); + try { + mColorDisplayManager.setSaturationLevel( + effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE + : SATURATION_LEVEL_FULL_COLOR); + } catch (Exception e) { + Slog.e(TAG, "Could not change grayscale override", e); + } } } if (mLastAppliedEffects.shouldDimWallpaper() != effects.shouldDimWallpaper()) { if (mWallpaperManager != null) { - mWallpaperManager.setWallpaperDimAmount( - effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED - : WALLPAPER_DIM_AMOUNT_NORMAL); + try { + mWallpaperManager.setWallpaperDimAmount( + effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED + : WALLPAPER_DIM_AMOUNT_NORMAL); + } catch (Exception e) { + Slog.e(TAG, "Could not change wallpaper override", e); + } } } if (mLastAppliedEffects.shouldUseNightMode() != effects.shouldUseNightMode()) { - updateOrScheduleNightMode(effects.shouldUseNightMode(), origin); + try { + updateOrScheduleNightMode(effects.shouldUseNightMode(), origin); + } catch (Exception e) { + Slog.e(TAG, "Could not change dark theme override", e); + } } }); @@ -131,9 +148,13 @@ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { private void updateNightModeImmediately(boolean useNightMode) { Binder.withCleanCallingIdentity(() -> { - mUiModeManager.setAttentionModeThemeOverlay( - useNightMode ? MODE_ATTENTION_THEME_OVERLAY_NIGHT - : MODE_ATTENTION_THEME_OVERLAY_OFF); + try { + mUiModeManager.setAttentionModeThemeOverlay( + useNightMode ? MODE_ATTENTION_THEME_OVERLAY_NIGHT + : MODE_ATTENTION_THEME_OVERLAY_OFF); + } catch (Exception e) { + Slog.e(TAG, "Could not change wallpaper override", e); + } }); } diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java index 3949dfe01761..1a8e44b526dd 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 @@ abstract public class ManagedServices { 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/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index ae29e1bba8d5..454bd20c59e2 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -149,6 +149,8 @@ public class ZenModeHelper { private static final String IMPLICIT_RULE_ID_PREFIX = "implicit_"; // + pkg_name + private static final int MAX_ICON_RESOURCE_NAME_LENGTH = 1000; + /** * Send new activation AutomaticZenRule statuses to apps with a min target SDK version */ @@ -2645,7 +2647,13 @@ public class ZenModeHelper { requireNonNull(packageName); try { final Resources res = mPm.getResourcesForApplication(packageName); - return res.getResourceName(resId); + String resourceName = res.getResourceName(resId); + if (resourceName != null && resourceName.length() > MAX_ICON_RESOURCE_NAME_LENGTH) { + Slog.e(TAG, "Resource name for ID=" + resId + " in package " + packageName + + " is too long (" + resourceName.length() + "); ignoring it"); + return null; + } + return resourceName; } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) { Slog.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName + ". Resource IDs may change when the application is upgraded, and the system" 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 000000000000..e2901c39e612 --- /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 dec97fb588a5..0d1095f5656d 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 @@ public class PackageArchiver { 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/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index a5cd821e319d..050d44eed2ea 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -1445,6 +1445,15 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements .createEvent(DevicePolicyEnums.UNINSTALL_PACKAGE) .setAdmin(callerPackageName) .write(); + } else if (PackageInstallerSession.isEmergencyInstallerEnabled(callerPackageName, snapshot, + userId, callingUid)) { + // Need to clear the calling identity to get DELETE_PACKAGES permission + final long ident = Binder.clearCallingIdentity(); + try { + mPm.deletePackageVersioned(versionedPackage, adapter.getBinder(), userId, flags); + } finally { + Binder.restoreCallingIdentity(ident); + } } else { ApplicationInfo appInfo = snapshot.getApplicationInfo(callerPackageName, 0, userId); if (appInfo.targetSdkVersion >= Build.VERSION_CODES.P) { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index a90473865ce5..06065638dc11 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -316,14 +316,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final int INCREMENTAL_STORAGE_UNHEALTHY_MONITORING_MS = 60000; /** - * If an app being installed targets {@link Build.VERSION_CODES#S API 31} and above, the app - * can be installed without user action. + * If an app being installed targets {@link Build.VERSION_CODES#TIRAMISU API 33} and above, + * the app can be installed without user action. * See {@link PackageInstaller.SessionParams#setRequireUserAction} for other conditions required * to be satisfied for a silent install. */ @ChangeId - @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S) - private static final long SILENT_INSTALL_ALLOWED = 265131695L; + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + private static final long SILENT_INSTALL_ALLOWED = 325888262L; /** * The system supports pre-approval and update ownership features from @@ -966,7 +966,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { getInstallSource().mInstallerPackageName, mInstallerUid); } - private boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot) { + static boolean isEmergencyInstallerEnabled(String packageName, Computer snapshot, int userId, + int installerUid) { final PackageStateInternal ps = snapshot.getPackageStateInternal(packageName); if (ps == null || ps.getPkg() == null || !ps.isSystem()) { return false; @@ -974,7 +975,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { int uid = UserHandle.getUid(userId, ps.getAppId()); String emergencyInstaller = ps.getPkg().getEmergencyInstaller(); if (emergencyInstaller == null || !ArrayUtils.contains( - snapshot.getPackagesForUid(mInstallerUid), emergencyInstaller)) { + snapshot.getPackagesForUid(installerUid), emergencyInstaller)) { return false; } // Only system installers can have an emergency installer @@ -987,7 +988,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { return false; } return (snapshot.checkUidPermission(Manifest.permission.EMERGENCY_INSTALL_PACKAGES, - mInstallerUid) == PackageManager.PERMISSION_GRANTED); + installerUid) == PackageManager.PERMISSION_GRANTED); } private static final int USER_ACTION_NOT_NEEDED = 0; @@ -1075,7 +1076,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { getInstallerPackageName()); final boolean isSelfUpdate = targetPackageUid == mInstallerUid; final boolean isEmergencyInstall = - isEmergencyInstallerEnabled(packageName, snapshot); + isEmergencyInstallerEnabled(packageName, snapshot, userId, mInstallerUid); final boolean isPermissionGranted = isInstallPermissionGranted || (isUpdatePermissionGranted && isUpdate) || (isSelfUpdatePermissionGranted && isSelfUpdate) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f8fceda0582c..679ab65380af 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService 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 @@ public class PackageManagerService implements PackageSender, TestUtilityService return; } - // Log the metrics when the component state is changed. - PackageMetrics.reportComponentStateChanged(computer, componentStateMetricsList, userId); - if (isSynchronous) { flushPackageRestrictionsAsUserInternalLocked(userId); } else { @@ -4074,6 +4076,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService } } + // 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/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 0a8b2b2c6219..c40563f2abeb 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -3380,7 +3380,7 @@ class PackageManagerShellCommand extends ShellCommand { params.sessionParams = sessionParams; // Allowlist all permissions by default sessionParams.installFlags |= PackageManager.INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS; - // Set package source to other by default + // Set package source to other by default. Can be overridden by "--package-source" sessionParams.setPackageSource(PackageInstaller.PACKAGE_SOURCE_OTHER); // Encodes one of the states: @@ -3567,6 +3567,9 @@ class PackageManagerShellCommand extends ShellCommand { case "--ignore-dexopt-profile": sessionParams.installFlags |= PackageManager.INSTALL_IGNORE_DEXOPT_PROFILE; break; + case "--package-source": + sessionParams.setPackageSource(Integer.parseInt(getNextArg())); + break; default: throw new IllegalArgumentException("Unknown option " + opt); } diff --git a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java index a96e01bdeadf..33ea56399381 100644 --- a/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/MobileRadioPowerStatsCollector.java @@ -195,6 +195,9 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { return null; } + Arrays.fill(mPowerStats.stats, 0); + mPowerStats.uidStats.clear(); + collectModemActivityInfo(); collectNetworkStats(); @@ -239,16 +242,16 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { activityInfo = null; } + if (activityInfo == null) { + return; + } + ModemActivityInfo deltaInfo = mLastModemActivityInfo == null - ? (activityInfo == null ? null : activityInfo.getDelta(activityInfo)) + ? activityInfo.getDelta(activityInfo) : mLastModemActivityInfo.getDelta(activityInfo); mLastModemActivityInfo = activityInfo; - if (deltaInfo == null) { - return; - } - setTimestamp(deltaInfo.getTimestampMillis()); mLayout.setDeviceSleepTime(mDeviceStats, deltaInfo.getSleepTimeMillis()); mLayout.setDeviceIdleTime(mDeviceStats, deltaInfo.getIdleTimeMillis()); @@ -293,8 +296,6 @@ public class MobileRadioPowerStatsCollector extends PowerStatsCollector { } private void collectNetworkStats() { - mPowerStats.uidStats.clear(); - NetworkStats networkStats = mNetworkStatsSupplier.get(); if (networkStats == null) { return; diff --git a/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java b/services/core/java/com/android/server/rollback/RollbackManagerServiceImpl.java index c8bcc5128c3a..e753ce845ddc 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 @@ class RollbackManagerServiceImpl extends IRollbackManager.Stub implements Rollba } 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/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java index c1d92cffe1a7..68eb8eb1deaf 100644 --- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java +++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java @@ -93,6 +93,7 @@ public class TracingServiceProxy extends SystemService { private final Context mContext; private final PackageManager mPackageManager; private final LruCache<ComponentName, ServiceConnector<IMessenger>> mCachedReporterServices; + private boolean mServicePublished = false; private final ITracingServiceProxy.Stub mTracingServiceProxy = new ITracingServiceProxy.Stub() { /** @@ -122,9 +123,12 @@ public class TracingServiceProxy extends SystemService { public void onStart() {} @Override - public void onBootPhase(int phase) { - if (phase == SystemService.PHASE_THIRD_PARTY_APPS_CAN_START) { + public void onUserUnlocking(@NonNull TargetUser user) { + // We need the device storage to be unlocked before we can accept and forward + // requests. + if (!mServicePublished) { publishBinderService(TRACING_SERVICE_PROXY_BINDER_NAME, mTracingServiceProxy); + mServicePublished = true; } } diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index e00e81371853..04db3e83ac71 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.ArrayMap; 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.internal.widget.LockPatternUtils; 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.io.PrintWriter; 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 @@ public class TrustManagerService extends SystemService { return KeyStoreAuthorization.getInstance(); } + AlarmManager getAlarmManager() { + return mContext.getSystemService(AlarmManager.class); + } + Looper getLooper() { return Looper.myLooper(); } @@ -293,7 +297,7 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { } 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { /* 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 @@ public class TrustManagerService extends SystemService { } 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { } 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 @@ public class TrustManagerService extends SystemService { // 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { } 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 @@ public class TrustManagerService extends SystemService { } 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 class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { (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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { } 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { } 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { } } + 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { } 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { // 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 @@ public class TrustManagerService extends SystemService { } 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { && 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 @@ public class TrustManagerService extends SystemService { && (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 @@ public class TrustManagerService extends SystemService { // 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { @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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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 @@ public class TrustManagerService extends SystemService { 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/uri/UriPermission.java b/services/core/java/com/android/server/uri/UriPermission.java index e406eb2c2a48..0d1f36794f49 100644 --- a/services/core/java/com/android/server/uri/UriPermission.java +++ b/services/core/java/com/android/server/uri/UriPermission.java @@ -223,7 +223,9 @@ final class UriPermission { if (mWriteOwners != null && includingOwners) { ownedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; for (UriPermissionOwner r : mWriteOwners) { - r.removeWritePermission(this); + if (r != null) { + r.removeWritePermission(this); + } } mWriteOwners = null; } @@ -348,7 +350,7 @@ final class UriPermission { if (mWriteOwners != null) { pw.print(prefix); pw.println("writeOwners:"); - for (UriPermissionOwner owner : mReadOwners) { + for (UriPermissionOwner owner : mWriteOwners) { pw.print(prefix); pw.println(" * " + owner); } diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java index 8f755f4ecec8..f9bad59d38b0 100644 --- a/services/core/java/com/android/server/vibrator/HalVibration.java +++ b/services/core/java/com/android/server/vibrator/HalVibration.java @@ -133,6 +133,7 @@ final class HalVibration extends Vibration { // Save scale values for debugging purposes. mScaleLevel = scaler.getScaleLevel(vibrationUsage); mAdaptiveScale = scaler.getAdaptiveHapticsScale(vibrationUsage); + stats.reportAdaptiveScale(mAdaptiveScale); // Scale all VibrationEffect instances in given CombinedVibration. CombinedVibration newEffect = mEffectToPlay.transform(scaler::scale, vibrationUsage); diff --git a/services/core/java/com/android/server/vibrator/VibrationStats.java b/services/core/java/com/android/server/vibrator/VibrationStats.java index 2d003513bee1..dd66809e7ae6 100644 --- a/services/core/java/com/android/server/vibrator/VibrationStats.java +++ b/services/core/java/com/android/server/vibrator/VibrationStats.java @@ -61,6 +61,10 @@ final class VibrationStats { private int mEndedByUsage; private int mInterruptedUsage; + // Vibration parameters. + // Set by VibrationThread only (single-threaded). + private float mAdaptiveScale; + // All following counters are set by VibrationThread only (single-threaded): // Counts how many times the VibrationEffect was repeated. private int mRepeatCount; @@ -188,6 +192,14 @@ final class VibrationStats { } } + /** Report the adaptive scale that was applied to this vibration. */ + void reportAdaptiveScale(float scale) { + // Only report adaptive scale if it was set for this vibration. + if (Float.compare(scale, VibrationScaler.ADAPTIVE_SCALE_NONE) != 0) { + mAdaptiveScale = scale; + } + } + /** Report the vibration has looped a few more times. */ void reportRepetition(int loops) { mRepeatCount += loops; @@ -287,6 +299,7 @@ final class VibrationStats { public final int vibrationType; public final int usage; public final int status; + public final float adaptiveScale; public final boolean endedBySameUid; public final int endedByUsage; public final int interruptedUsage; @@ -316,6 +329,7 @@ final class VibrationStats { this.vibrationType = vibrationType; this.usage = usage; this.status = status.getProtoEnumValue(); + this.adaptiveScale = stats.mAdaptiveScale; endedBySameUid = (uid == stats.mEndedByUid); endedByUsage = stats.mEndedByUsage; interruptedUsage = stats.mInterruptedUsage; @@ -376,7 +390,7 @@ final class VibrationStats { halOnCount, halOffCount, halPerformCount, halSetAmplitudeCount, halSetExternalControlCount, halSupportedCompositionPrimitivesUsed, halSupportedEffectsUsed, halUnsupportedCompositionPrimitivesUsed, - halUnsupportedEffectsUsed, halCompositionSize, halPwleSize); + halUnsupportedEffectsUsed, halCompositionSize, halPwleSize, adaptiveScale); } private static int[] filteredKeys(SparseBooleanArray supportArray, boolean supported) { diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 09c24931d641..3dcc7a6f76e5 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -1628,6 +1628,12 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mStatus = Vibration.Status.RUNNING; } + public void scale(VibrationScaler scaler, int usage) { + scale.scaleLevel = scaler.getScaleLevel(usage); + scale.adaptiveHapticsScale = scaler.getAdaptiveHapticsScale(usage); + stats.reportAdaptiveScale(scale.adaptiveHapticsScale); + } + public void mute() { externalVibration.mute(); } @@ -2044,9 +2050,7 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mCurrentExternalVibration = vibHolder; vibHolder.linkToDeath(); - vibHolder.scale.scaleLevel = mVibrationScaler.getScaleLevel(attrs.getUsage()); - vibHolder.scale.adaptiveHapticsScale = - mVibrationScaler.getAdaptiveHapticsScale(attrs.getUsage()); + vibHolder.scale(mVibrationScaler, attrs.getUsage()); } if (waitForCompletion) { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 434e92f7978a..2d2a88a866ba 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } 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 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // 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 @@ -8551,7 +8553,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds // are already calculated in resolveFixedOrientationConfiguration. // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer. - if (!isLetterboxedForFixedOrientationAndAspectRatio() + if (Flags.immersiveAppRepositioning() && !isLetterboxedForFixedOrientationAndAspectRatio() && !mLetterboxUiController.hasFullscreenOverride()) { resolveAspectRatioRestriction(newParentConfiguration); } @@ -8568,6 +8570,14 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A computeConfigByResolveHint(resolvedConfig, newParentConfiguration); } } + // If activity in fullscreen mode is letterboxed because of fixed orientation then bounds + // are already calculated in resolveFixedOrientationConfiguration, or if in size compat + // mode, it should already be calculated in resolveSizeCompatModeConfiguration. + // Don't apply aspect ratio if app is overridden to fullscreen by device user/manufacturer. + if (!Flags.immersiveAppRepositioning() && !isLetterboxedForFixedOrientationAndAspectRatio() + && !mInSizeCompatModeForBounds && !mLetterboxUiController.hasFullscreenOverride()) { + resolveAspectRatioRestriction(newParentConfiguration); + } if (isFixedOrientationLetterboxAllowed || compatDisplayInsets != null // In fullscreen, can be letterboxed for aspect ratio. @@ -8903,7 +8913,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A } boolean isImmersiveMode(@NonNull Rect parentBounds) { - if (!mResolveConfigHint.mUseOverrideInsetsForConfig) { + if (!Flags.immersiveAppRepositioning()) { + return false; + } + if (!mResolveConfigHint.mUseOverrideInsetsForConfig + && mWmService.mFlags.mInsetsDecoupledConfiguration) { return false; } final Insets navBarInsets = mDisplayContent.getInsetsStateController() @@ -9247,17 +9261,35 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A @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 // activity will be displayed within them even if it is in size compat mode. They should be // saved here before resolved bounds are overridden below. - final Rect containerBounds = isAspectRatioApplied() + final boolean useResolvedBounds = Flags.immersiveAppRepositioning() + ? isAspectRatioApplied() : isLetterboxedForFixedOrientationAndAspectRatio(); + final Rect containerBounds = useResolvedBounds ? new Rect(resolvedBounds) : newParentConfiguration.windowConfiguration.getBounds(); - final Rect containerAppBounds = isAspectRatioApplied() + 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/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java index 4149bd9c6c56..351dc3aee0c3 100644 --- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java +++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java @@ -41,7 +41,7 @@ class ActivitySecurityModelFeatureFlags { static final String DOC_LINK = "go/android-asm"; /** Used to determine which version of the ASM logic was used in logs while we iterate */ - static final int ASM_VERSION = 10; + static final int ASM_VERSION = 11; private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER; private static final String KEY_ASM_PREFIX = "ActivitySecurity__"; diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 207707efb51d..19d7a3c8d86c 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -28,8 +28,10 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED; 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; +import static android.security.Flags.asmOptSystemIntoEnforcement; import static com.android.server.wm.ActivityStarter.ASM_RESTRICTIONS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS; @@ -385,6 +387,10 @@ public class BackgroundActivityStartController { 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( @@ -1457,13 +1463,18 @@ public class BackgroundActivityStartController { return bas.matchesSource(); } + if (ar.isUid(SYSTEM_UID)) { + if (asmOptSystemIntoEnforcement()) { + return bas.optedIn(ar); + } else { + return bas; + } + } + if (!CompatChanges.isChangeEnabled(ASM_RESTRICTIONS, ar.getUid())) { return bas; } - if (ar.isUid(SYSTEM_UID)) { - return bas.optedIn(ar); - } String packageName = ar.packageName; if (packageName == null) { @@ -1561,6 +1572,7 @@ public class BackgroundActivityStartController { joiner.add(prefix + "Allowed By Grace Period: " + allowedByGracePeriod); joiner.add(prefix + "LastResumedActivity: " + recordToString.apply(mService.mLastResumedActivity)); + joiner.add(prefix + "System opted into enforcement: " + asmOptSystemIntoEnforcement()); if (mTopFinishedActivity != null) { joiner.add(prefix + "TopFinishedActivity: " + mTopFinishedActivity.mDebugInfo); diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 9dc9ad4a2ba2..6c48e9586fd9 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 @@ class Task extends TaskFragment { 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 d4bbe846c4b9..61022cc971e2 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 @@ class TaskFragment extends WindowContainer<WindowContainer> { // 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/Transition.java b/services/core/java/com/android/server/wm/Transition.java index d8e4aa25dd77..c972eee84ea3 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -3787,7 +3787,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (changeInfo.mRotation != wc.mDisplayContent.getRotation()) { // This isn't cheap, so only do it for rotation change. changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma( - buffer, screenshotBuffer.getColorSpace()); + buffer, screenshotBuffer.getColorSpace(), wc.mSurfaceControl); } SurfaceControl.Transaction t = wc.mWmService.mTransactionFactory.get(); TransitionAnimation.configureScreenshotLayer(t, snapshotSurface, screenshotBuffer); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 0bf1c88d5b4f..a00b6fc47de7 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub 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 @@ public class WindowManagerService extends IWindowManager.Stub } 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 @@ public class WindowManagerService extends IWindowManager.Stub } 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 6953c60d0d74..d1efc27d9661 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP /** @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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } 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 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP 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 ddbc5354358c..e42f7f8be0ca 100644 --- a/services/core/jni/BroadcastRadio/convert.cpp +++ b/services/core/jni/BroadcastRadio/convert.cpp @@ -433,7 +433,7 @@ static JavaRef<jobject> BandDescriptorFromHal(JNIEnv *env, const V1_0::BandConfi 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 @@ JavaRef<jobject> BandConfigFromHal(JNIEnv *env, const V1_0::BandConfig &config, 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 @@ JavaRef<jobject> MetadataFromHal(JNIEnv *env, const hidl_vec<V1_0::MetaData> &me 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/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java index e763c9eccceb..669a999c921e 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java @@ -1588,7 +1588,7 @@ final class DevicePolicyEngine { private Set<EnforcingAdmin> getEnforcingAdminsOnUser(int userId) { synchronized (mLock) { return mEnforcingAdmins.contains(userId) - ? mEnforcingAdmins.get(userId) : Collections.emptySet(); + ? new HashSet<>(mEnforcingAdmins.get(userId)) : Collections.emptySet(); } } diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java index 7483b43baf13..c7fd979908b9 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 @@ public final class PersonalAppsSuspensionHelper { 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 927df8bb23e7..cfe4e17eb1be 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -761,9 +761,6 @@ public final class SystemServer implements Dumpable { } } - 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 @@ public final class SystemServer implements Dumpable { 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 b0c7073d792b..3bdcd9b0fdcd 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 @@ package com.android.server.permission.access.appop 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 @@ class AppOpService(private val service: AccessCheckingService) : AppOpsCheckingS ) { 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 8db896b76f6c..bba4c8d64c86 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 @@ public class DisplayBrightnessStateTest { .setShouldUseAutoBrightness(shouldUseAutoBrightness) .setShouldUpdateScreenBrightnessSetting(shouldUpdateScreenBrightnessSetting) .setBrightnessAdjustmentFlag(brightnessAdjustmentFlag) + .setIsUserInitiatedChange(true) .build(); assertEquals(displayBrightnessState.getBrightness(), brightness, FLOAT_DELTA); @@ -109,7 +110,9 @@ public class DisplayBrightnessStateTest { .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 12050e1beaed..01ff35fc088c 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 @@ public class LocalDisplayAdapterTest { } @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 c5105e78d0b3..323ef6a28e14 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 @@ public final class DisplayBrightnessControllerTest { 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 7a6a91108282..4d5ff55cc805 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 @@ public class AutoBrightnessFallbackStrategyTest { .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 afb5a5cc5cc4..8a33f341df56 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 @@ public class AutomaticBrightnessStrategyTest { .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 @@ public class AutomaticBrightnessStrategyTest { .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 47f174684771..353432539587 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 @@ public class BoostBrightnessStrategyTest { .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 9246780a8516..bd6d8be45fcb 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 @@ public class DozeBrightnessStrategyTest { .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 c4767ae5172b..4cae35d11853 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 @@ public class FallbackBrightnessStrategyTest { .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 682c9cc980d2..fdaf8f6a7808 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 @@ public class FollowerBrightnessStrategyTest { .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 ccf630948aac..e3c276025e51 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 @@ public class OffloadBrightnessStrategyTest { .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 8e7b4636a392..ebe407b2445b 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 @@ public class OverrideBrightnessStrategyTest { .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 e799d0e31165..4bad5696861f 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 @@ public final class ScreenOffBrightnessStrategyTest { .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 aaada4115a56..5410a20e9d75 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 @@ public class TemporaryBrightnessStrategyTest { .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 cf6146f2f35e..34c6ba90a0ba 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 @@ class AppRequestObserverTest { 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 000000000000..dbe9e4ae5ef5 --- /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 5da1bb63548b..dd5e1be8462c 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 @@ class VoteSummaryTest { 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 0532e04257d4..2a670294a470 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.eq; 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.CheckFlagsRule; 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 com.android.server.SystemServiceManager; 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 @@ public class TrustManagerServiceTest { 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 @@ public class TrustManagerServiceTest { 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 @@ public class TrustManagerServiceTest { 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 @@ public class TrustManagerServiceTest { 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 @@ public class TrustManagerServiceTest { } @Override + AlarmManager getAlarmManager() { + return mAlarmManager; + } + + @Override Looper getLooper() { return mHandlerThread.getLooper(); } @@ -367,12 +407,10 @@ public class TrustManagerServiceTest { @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 @@ public class TrustManagerServiceTest { } @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 @@ public class TrustManagerServiceTest { 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 c4946f0b221e..8914696d55da 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 @@ public class AccessibilityServiceConnectionTest { } @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 52b33db556e6..d4f0d5aa7ef6 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 @@ public class ProxyManagerTest { } @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 87fe6cf8f283..0de5807067e6 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 android.view.accessibility.MagnificationAnimationCallback; 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 @@ import org.mockito.invocation.InvocationOnMock; */ 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 @@ public class MagnificationConnectionManagerTest { 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 @@ public class MagnificationConnectionManagerTest { } @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 e756082bc912..758c84a26dcd 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.spy; 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.AudioManager; 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 @@ public class AbsoluteVolumeBehaviorTest { 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 3623012b348f..2cb02bdd2806 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.mock; 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 @@ public class AudioDeviceVolumeManagerTest { 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 000000000000..8d772ad5c124 --- /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 634877eb2539..037c3c00443c 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 @@ public class AudioServiceTest { @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 @@ public class AudioServiceTest { .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 8dfcc1843fed..27b552fa7cdd 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 com.google.common.truth.Truth.assertWithMessage; 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 @@ public class DeviceVolumeBehaviorTest { 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/ServiceHolderTest.java b/services/tests/servicestests/src/com/android/server/audio/ServiceHolderTest.java new file mode 100644 index 000000000000..39f19ae1b382 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/ServiceHolderTest.java @@ -0,0 +1,284 @@ +/* + * 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 com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.media.IAudioPolicyService; +import android.os.Binder; +import android.os.IBinder; +import android.os.IServiceCallback; +import android.os.RemoteException; +import android.platform.test.annotations.Presubmit; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.ArgumentMatchers; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +import java.util.concurrent.Executor; +import java.util.function.Consumer; +import java.util.function.Function; + +@RunWith(AndroidJUnit4.class) +@Presubmit +public class ServiceHolderTest { + + private static final String AUDIO_POLICY_SERVICE_NAME = "media.audio_policy"; + + @Rule + public final MockitoRule mockito = MockitoJUnit.rule(); + + // the actual class under test + private ServiceHolder<IAudioPolicyService> mServiceHolder; + + @Mock private ServiceHolder.ServiceProviderFacade mServiceProviderFacade; + + @Mock private IAudioPolicyService mAudioPolicyService; + + @Mock private IBinder mBinder; + + @Mock private Consumer<IAudioPolicyService> mTaskOne; + @Mock private Consumer<IAudioPolicyService> mTaskTwo; + + @Before + public void setUp() throws Exception { + mServiceHolder = + new ServiceHolder( + AUDIO_POLICY_SERVICE_NAME, + (Function<IBinder, IAudioPolicyService>) + binder -> { + if (binder == mBinder) { + return mAudioPolicyService; + } else { + return mock(IAudioPolicyService.class); + } + }, + r -> r.run(), + mServiceProviderFacade); + when(mAudioPolicyService.asBinder()).thenReturn(mBinder); + } + + @Test + public void testListenerRegistered_whenConstructed() { + verify(mServiceProviderFacade) + .registerForNotifications(eq(AUDIO_POLICY_SERVICE_NAME), ArgumentMatchers.any()); + } + + @Test + public void testServiceSuccessfullyPopulated_whenCallback() throws RemoteException { + initializeViaCallback(); + verify(mBinder).linkToDeath(any(), anyInt()); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + } + + @Test + public void testCheckServiceCalled_whenUncached() { + when(mServiceProviderFacade.checkService(eq(AUDIO_POLICY_SERVICE_NAME))) + .thenReturn(mBinder); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + } + + @Test + public void testCheckServiceTransmitsNull() { + assertThat(mServiceHolder.checkService()).isEqualTo(null); + } + + @Test + public void testWaitForServiceCalled_whenUncached() { + when(mServiceProviderFacade.waitForService(eq(AUDIO_POLICY_SERVICE_NAME))) + .thenReturn(mBinder); + assertThat(mServiceHolder.waitForService()).isEqualTo(mAudioPolicyService); + } + + @Test + public void testCheckServiceNotCalled_whenCached() { + initializeViaCallback(); + mServiceHolder.checkService(); + verify(mServiceProviderFacade, never()).checkService(any()); + } + + @Test + public void testWaitForServiceNotCalled_whenCached() { + initializeViaCallback(); + mServiceHolder.waitForService(); + verify(mServiceProviderFacade, never()).waitForService(any()); + } + + @Test + public void testStartTaskCalled_onStart() { + mServiceHolder.registerOnStartTask(mTaskOne); + mServiceHolder.registerOnStartTask(mTaskTwo); + mServiceHolder.unregisterOnStartTask(mTaskOne); + when(mServiceProviderFacade.checkService(eq(AUDIO_POLICY_SERVICE_NAME))) + .thenReturn(mBinder); + + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + + verify(mTaskTwo).accept(eq(mAudioPolicyService)); + verify(mTaskOne, never()).accept(any()); + } + + @Test + public void testStartTaskCalled_onStartFromCallback() { + mServiceHolder.registerOnStartTask(mTaskOne); + mServiceHolder.registerOnStartTask(mTaskTwo); + mServiceHolder.unregisterOnStartTask(mTaskOne); + + initializeViaCallback(); + + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + verify(mTaskTwo).accept(eq(mAudioPolicyService)); + verify(mTaskOne, never()).accept(any()); + } + + @Test + public void testStartTaskCalled_onRegisterAfterStarted() { + initializeViaCallback(); + mServiceHolder.registerOnStartTask(mTaskOne); + verify(mTaskOne).accept(eq(mAudioPolicyService)); + } + + @Test + public void testBinderDied_clearsServiceAndUnlinks() { + initializeViaCallback(); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + + mServiceHolder.binderDied(mBinder); + + verify(mBinder).unlinkToDeath(any(), anyInt()); + assertThat(mServiceHolder.checkService()).isEqualTo(null); + verify(mServiceProviderFacade).checkService(eq(AUDIO_POLICY_SERVICE_NAME)); + } + + @Test + public void testBinderDied_callsDeathTasks() { + mServiceHolder.registerOnDeathTask(mTaskOne); + mServiceHolder.registerOnDeathTask(mTaskTwo); + initializeViaCallback(); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + mServiceHolder.unregisterOnDeathTask(mTaskOne); + + mServiceHolder.binderDied(mBinder); + + verify(mTaskTwo).accept(eq(mAudioPolicyService)); + verify(mTaskOne, never()).accept(any()); + } + + @Test + public void testAttemptClear_clearsServiceAndUnlinks() { + initializeViaCallback(); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + + mServiceHolder.attemptClear(mBinder); + + verify(mBinder).unlinkToDeath(any(), anyInt()); + assertThat(mServiceHolder.checkService()).isEqualTo(null); + verify(mServiceProviderFacade).checkService(eq(AUDIO_POLICY_SERVICE_NAME)); + } + + @Test + public void testAttemptClear_callsDeathTasks() { + mServiceHolder.registerOnDeathTask(mTaskOne); + mServiceHolder.registerOnDeathTask(mTaskTwo); + initializeViaCallback(); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + mServiceHolder.unregisterOnDeathTask(mTaskOne); + + mServiceHolder.attemptClear(mBinder); + + verify(mTaskTwo).accept(eq(mAudioPolicyService)); + verify(mTaskOne, never()).accept(any()); + } + + @Test + public void testSet_whenServiceSet_isIgnored() { + mServiceHolder.registerOnStartTask(mTaskOne); + when(mServiceProviderFacade.checkService(eq(AUDIO_POLICY_SERVICE_NAME))) + .thenReturn(mBinder); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + + verify(mTaskOne).accept(eq(mAudioPolicyService)); + + // get the callback + ArgumentCaptor<IServiceCallback> cb = ArgumentCaptor.forClass(IServiceCallback.class); + verify(mServiceProviderFacade) + .registerForNotifications(eq(AUDIO_POLICY_SERVICE_NAME), cb.capture()); + + // Simulate a service callback with a different instance + try { + cb.getValue().onRegistration(AUDIO_POLICY_SERVICE_NAME, new Binder()); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + + // No additional start task call (i.e. only the first verify) + verify(mTaskOne).accept(any()); + // Same instance + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + + } + + @Test + public void testClear_whenServiceCleared_isIgnored() { + mServiceHolder.registerOnDeathTask(mTaskOne); + mServiceHolder.attemptClear(mBinder); + verify(mTaskOne, never()).accept(any()); + } + + @Test + public void testClear_withDifferentCookie_isIgnored() { + mServiceHolder.registerOnDeathTask(mTaskOne); + initializeViaCallback(); + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + + // Notif for stale cookie + mServiceHolder.attemptClear(new Binder()); + + // Service shouldn't be cleared + assertThat(mServiceHolder.checkService()).isEqualTo(mAudioPolicyService); + // No death tasks should fire + verify(mTaskOne, never()).accept(any()); + } + + private void initializeViaCallback() { + ArgumentCaptor<IServiceCallback> cb = ArgumentCaptor.forClass(IServiceCallback.class); + verify(mServiceProviderFacade) + .registerForNotifications(eq(AUDIO_POLICY_SERVICE_NAME), cb.capture()); + + try { + cb.getValue().onRegistration(AUDIO_POLICY_SERVICE_NAME, mBinder); + } catch (RemoteException e) { + throw new RuntimeException(e); + } + } +} 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 23728db34c34..8e34ee1b6a42 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.ACTION_DOWN; 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 @@ public class VolumeHelperTest { @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 @@ public class VolumeHelperTest { 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 @@ public class VolumeHelperTest { 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 @@ public class VolumeHelperTest { } @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 @@ public class VolumeHelperTest { @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 7dd1847114c8..50cfa753ebdb 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.assertEquals; 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.ArraySet; 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 org.xmlpull.v1.XmlPullParserException; 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 @@ public class LocaleManagerBackupRestoreTest { 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 @@ public class LocaleManagerBackupRestoreTest { @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 @@ public class LocaleManagerBackupRestoreTest { @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 @@ public class LocaleManagerBackupRestoreTest { 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 @@ public class LocaleManagerBackupRestoreTest { 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 @@ public class LocaleManagerBackupRestoreTest { 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 @@ public class LocaleManagerBackupRestoreTest { } @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 @@ public class LocaleManagerBackupRestoreTest { 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 @@ public class LocaleManagerBackupRestoreTest { } } - 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 void checkStageDataDoesNotExist(int userId) { + assertTrue(mStagedDataFiles.get(userId) == null || !mStagedDataFiles.get(userId).exists()); + } + + private void checkArchivedFileExists() { + assertTrue(mArchivedPackageFile.exists()); } - private static void checkStageDataDoesNotExist(int userId) { - assertNull(STAGE_DATA.get(userId)); + 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 9f7cbe3170f0..b46902d9904a 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.content.pm.PackageManager; import android.os.HandlerThread; import android.util.SparseArray; +import java.io.File; import java.time.Clock; /** @@ -33,9 +34,9 @@ public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper { 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 64e62369f955..17b499e112bc 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 @@ package com.android.server.locksettings; 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.assertNotNull; 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 androidx.test.runner.AndroidJUnit4; 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 @@ public class RebootEscrowManagerTests { private Context mContext; private UserManager mUserManager; + private UserManagerInternal mUserManagerInternal; private RebootEscrowManager.Callbacks mCallbacks; private IRebootEscrow mRebootEscrow; private ResumeOnRebootServiceConnection mServiceConnection; @@ -126,13 +130,15 @@ public class RebootEscrowManagerTests { 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 @@ public class RebootEscrowManagerTests { 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 @@ public class RebootEscrowManagerTests { }; 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 @@ public class RebootEscrowManagerTests { mDefaultRebootEscrowProvider = new RebootEscrowProviderServerBasedImpl( storage, injector); mUserManager = userManager; + mUserManagerInternal = userManagerInternal; mKeyStoreManager = keyStoreManager; mInjected = injected; } @@ -202,6 +217,11 @@ public class RebootEscrowManagerTests { } @Override + public UserManagerInternal getUserManagerInternal() { + return mUserManagerInternal; + } + + @Override public boolean serverBasedResumeOnReboot() { return mServerBased; } @@ -289,8 +309,8 @@ public class RebootEscrowManagerTests { @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 class RebootEscrowManagerTests { 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 @@ public class RebootEscrowManagerTests { 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 @@ public class RebootEscrowManagerTests { 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 @@ public class RebootEscrowManagerTests { } @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/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java index bfbc81ccfe39..4bea95f61c1f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java @@ -25,10 +25,12 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; @@ -322,4 +324,26 @@ public class DefaultDeviceEffectsApplierTest { argThat(filter -> Intent.ACTION_SCREEN_OFF.equals(filter.getAction(0))), anyInt()); } + + @Test + public void apply_servicesThrow_noCrash() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + doThrow(new RuntimeException()).when(mPowerManager) + .suppressAmbientDisplay(anyString(), anyBoolean()); + doThrow(new RuntimeException()).when(mColorDisplayManager).setSaturationLevel(anyInt()); + doThrow(new RuntimeException()).when(mWallpaperManager).setWallpaperDimAmount(anyFloat()); + doThrow(new RuntimeException()).when(mUiModeManager).setAttentionModeThemeOverlay(anyInt()); + mApplier = new DefaultDeviceEffectsApplier(mContext); + + ZenDeviceEffects effects = new ZenDeviceEffects.Builder() + .setShouldSuppressAmbientDisplay(true) + .setShouldDimWallpaper(true) + .setShouldDisplayGrayscale(true) + .setShouldUseNightMode(true) + .build(); + mApplier.apply(effects, UPDATE_ORIGIN_USER); + + // No crashes + } } 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 c7c97e40b424..8a7d276dbecd 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.graphics.drawable.Icon; 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 org.mockito.MockitoAnnotations; 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 @@ public class GroupHelperTest extends UiServiceTestCase { 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 @@ public class GroupHelperTest extends UiServiceTestCase { } @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 @@ public class GroupHelperTest extends UiServiceTestCase { } @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 @@ public class GroupHelperTest extends UiServiceTestCase { } @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 @@ public class GroupHelperTest extends UiServiceTestCase { } @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 @@ public class GroupHelperTest extends UiServiceTestCase { } @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/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index e4188b04cc43..72ace84b855b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -6168,6 +6168,23 @@ public class ZenModeHelperTest extends UiServiceTestCase { .isEqualTo(previousManualZenPolicy); } + @Test + @EnableFlags(Flags.FLAG_MODES_API) + public void addRule_iconIdWithResourceNameTooLong_ignoresIcon() { + int resourceId = 999; + String veryLongResourceName = "com.android.server.notification:drawable/" + + "omg_this_is_one_long_resource_name".repeat(100); + when(mResources.getResourceName(resourceId)).thenReturn(veryLongResourceName); + when(mResources.getIdentifier(veryLongResourceName, null, null)).thenReturn(resourceId); + + String ruleId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), + new AutomaticZenRule.Builder("Rule", CONDITION_ID).setIconResId(resourceId).build(), + UPDATE_ORIGIN_APP, "reason", CUSTOM_PKG_UID); + AutomaticZenRule storedRule = mZenModeHelper.getAutomaticZenRule(ruleId); + + assertThat(storedRule.getIconResId()).isEqualTo(0); + } + private static void addZenRule(ZenModeConfig config, String id, String ownerPkg, int zenMode, @Nullable ZenPolicy zenPolicy) { ZenRule rule = new ZenRule(); 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 4a9760bc3317..e91fd3794a48 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 @@ public class ActivityRecordTests extends WindowTestsBase { 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/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 96ddfe8d5ac9..7ced9d50ab3f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -107,6 +107,7 @@ import android.graphics.Rect; import android.os.Binder; import android.os.RemoteException; import android.os.UserHandle; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsDisabled; import android.provider.DeviceConfig; @@ -401,6 +402,7 @@ public class SizeCompatTests extends WindowTestsBase { // TODO(b/333663877): Enable test after fix @Test @RequiresFlagsDisabled({Flags.FLAG_INSETS_DECOUPLED_CONFIGURATION}) + @EnableFlags(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING) public void testRepositionLandscapeImmersiveAppWithDisplayCutout() { final int dw = 2100; final int dh = 2000; @@ -4059,6 +4061,7 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @EnableFlags(Flags.FLAG_IMMERSIVE_APP_REPOSITIONING) public void testImmersiveLetterboxAlignedToBottom_OverlappingNavbar() { assertLandscapeActivityAlignedToBottomWithNavbar(true /* immersive */); } diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java index d5db61233230..65e8e13036a6 100644 --- a/telephony/java/android/telephony/CarrierRestrictionRules.java +++ b/telephony/java/android/telephony/CarrierRestrictionRules.java @@ -555,10 +555,11 @@ public final class CarrierRestrictionRules implements Parcelable { * 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 000000000000..ecd248cbf924 --- /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 000000000000..8a5e7f22888a --- /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 16983a0dbca1..b82396e710fd 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.INtnSignalStrengthConsumer; 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 @@ oneway interface ISatellite { * 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 a62363335fb9..d8b4974f23b9 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -262,6 +262,22 @@ public class SatelliteImplBase extends SatelliteService { "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 class SatelliteImplBase extends SatelliteService { 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 000000000000..22240f6324c9 --- /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 2cdf54248ebc..e09fbf6adc02 100644 --- a/tests/FlickerTests/ActivityEmbedding/Android.bp +++ b/tests/FlickerTests/ActivityEmbedding/Android.bp @@ -20,17 +20,65 @@ package { // 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 8a241de32a2b..209a14b3657d 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 @@ object Utils { clearCacheAfterParsing = false ) ) + .around(DisableNotificationCooldownSettingRule()) .around(PressHomeRule()) } } diff --git a/tests/FlickerTests/IME/Android.bp b/tests/FlickerTests/IME/Android.bp index 3538949cbc8d..ccc3683f0b93 100644 --- a/tests/FlickerTests/IME/Android.bp +++ b/tests/FlickerTests/IME/Android.bp @@ -39,6 +39,10 @@ android_test { 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 dc5013519dbf..ed6e8df3e293 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.FlickerBuilder 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 @@ class CloseImeShownOnAppStartToAppOnPressBackTest(flicker: LegacyFlickerTest) : @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 c29e71ce4c79..07fc2300286a 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 @@ package com.android.server.wm.flicker.notification 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.navBarWindowIsVisibleAtEnd 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 @@ open class OpenAppFromNotificationWarmTest(flicker: LegacyFlickerTest) : @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 a85d809257cd..c0cbdc3f96f8 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -31,6 +31,7 @@ android_test { "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 4893d14ad79b..6b95f5c10a1e 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 androidx.test.filters.MediumTest 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 @@ class AnrTest { 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 @@ class AnrTest { 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 a8b383cd4274..093923f3ed53 100644 --- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java +++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java @@ -101,8 +101,8 @@ public class PackageWatchdogTest { 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 @@ public class PackageWatchdogTest { 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 @@ public class PackageWatchdogTest { 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 30cf345db34d..2f6c0dd14adc 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 000000000000..2c9361df63fd --- /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 f1edca3ff86e..1ccdcc623c5b 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.core.app.ApplicationProvider.getApplicationContext 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 @@ import org.junit.runners.model.Statement /** * 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 @@ class ScreenLockRule : TestRule { dismissKeyguard() setScreenLock() setLockOnPowerButton() + configureStrongAuthState() try { base.evaluate() } finally { + restoreStrongAuthState() removeScreenLock() revertLockOnPowerButton() dismissKeyguard() @@ -57,6 +66,22 @@ class ScreenLockRule : TestRule { } } + 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 @@ class ScreenLockRule : TestRule { } } + 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 @@ class ScreenLockRule : TestRule { 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 18bc029b6845..404c6d968b3a 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.app.trust.TrustManager 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 @@ import kotlin.reflect.KClass * @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 @@ class TrustAgentRule<T : BaseTrustAgentService>( 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 @@ class TrustAgentRule<T : BaseTrustAgentService>( 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 @@ class TrustAgentRule<T : BaseTrustAgentService>( 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 @@ class TrustAgentRule<T : BaseTrustAgentService>( 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 index e047202f6740..3b32b47a6160 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 @@ internal fun wait( ) { 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 @@ internal fun wait( 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) + } +} |