diff options
33 files changed, 582 insertions, 133 deletions
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 8ddc1788a353..7885cd95ca25 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -900,6 +900,8 @@ public final class InputMethodInfo implements Parcelable { + Integer.toHexString(mIsDefaultResId)); pw.println(prefix + "Service:"); mService.dump(pw, prefix + " "); + pw.println(prefix + "InputMethodSubtype array: count=" + mSubtypes.getCount()); + mSubtypes.dump(pw, prefix + " "); } @Override diff --git a/core/java/android/view/inputmethod/InputMethodSubtype.java b/core/java/android/view/inputmethod/InputMethodSubtype.java index b0b94605f408..be91cfb9ebf8 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtype.java +++ b/core/java/android/view/inputmethod/InputMethodSubtype.java @@ -28,6 +28,7 @@ import android.icu.util.ULocale; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import android.util.Printer; import android.util.Slog; import com.android.internal.inputmethod.SubtypeLocaleUtils; @@ -791,6 +792,20 @@ public final class InputMethodSubtype implements Parcelable { dest.writeInt(mIsAsciiCapable ? 1 : 0); } + void dump(@NonNull Printer pw, @NonNull String prefix) { + pw.println(prefix + "mSubtypeNameOverride=" + mSubtypeNameOverride + + " mPkLanguageTag=" + mPkLanguageTag + + " mPkLayoutType=" + mPkLayoutType + + " mSubtypeId=" + mSubtypeId + + " mSubtypeLocale=" + mSubtypeLocale + + " mSubtypeLanguageTag=" + mSubtypeLanguageTag + + " mSubtypeMode=" + mSubtypeMode + + " mIsAuxiliary=" + mIsAuxiliary + + " mOverridesImplicitlyEnabledSubtype=" + mOverridesImplicitlyEnabledSubtype + + " mIsAsciiCapable=" + mIsAsciiCapable + + " mSubtypeHashCode=" + mSubtypeHashCode); + } + public static final @android.annotation.NonNull Parcelable.Creator<InputMethodSubtype> CREATOR = new Parcelable.Creator<InputMethodSubtype>() { @Override diff --git a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java index ee36dc72e346..c243a22b27b1 100644 --- a/core/java/android/view/inputmethod/InputMethodSubtypeArray.java +++ b/core/java/android/view/inputmethod/InputMethodSubtypeArray.java @@ -16,9 +16,11 @@ package android.view.inputmethod; +import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.os.BadParcelableException; import android.os.Parcel; +import android.util.Printer; import android.util.Slog; import java.io.ByteArrayInputStream; @@ -174,6 +176,19 @@ public class InputMethodSubtypeArray { private volatile byte[] mCompressedData; private volatile int mDecompressedSize; + void dump(@NonNull Printer pw, @NonNull String prefix) { + final var innerPrefix = prefix + " "; + for (int i = 0; i < mCount; i++) { + pw.println(prefix + "InputMethodSubtype #" + i + ":"); + final var subtype = get(i); + if (subtype != null) { + subtype.dump(pw, innerPrefix); + } else { + pw.println(innerPrefix + "missing subtype"); + } + } + } + private static byte[] marshall(final InputMethodSubtype[] array) { Parcel parcel = null; try { diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl index 6ffc63869f26..a2efbd29c54a 100644 --- a/core/java/com/android/internal/statusbar/IStatusBar.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl @@ -370,9 +370,10 @@ oneway interface IStatusBar /** * Enters stage split from a current running app. * + * @param displayId the id of the current display. * @param leftOrTop indicates where the stage split is. */ - void enterStageSplitFromRunningApp(boolean leftOrTop); + void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop); /** * Shows the media output switcher dialog. diff --git a/core/res/res/values-watch/colors.xml b/core/res/res/values-watch/colors.xml index 0b00bd8ea686..e2b7505f3c6e 100644 --- a/core/res/res/values-watch/colors.xml +++ b/core/res/res/values-watch/colors.xml @@ -18,11 +18,26 @@ <resources> <color name="system_error_light">#B3261E</color> <color name="system_on_error_light">#FFFFFF</color> - <color name="system_error_container_light">#F9DEDC</color> + <color name="system_error_container_light">#F7DCDA</color> <color name="system_on_error_container_light">#410E0B</color> - <color name="system_error_dark">#EC928E</color> - <color name="system_on_error_dark">#410E0B</color> - <color name="system_error_container_dark">#F2B8B5</color> - <color name="system_on_error_container_dark">#601410</color> + <color name="system_error_dark">#F2B8B5</color> + <color name="system_on_error_dark">#601410</color> + <color name="system_error_container_dark">#FF8986</color> + <color name="system_on_error_container_dark">#410E0B</color> + + <!-- With material deprecation of 'background' in favor of 'surface' we flatten these + on watches to match the black background requirements --> + <color name="system_surface_dark">#000000</color> + <color name="system_surface_dim_dark">#000000</color> + <color name="system_surface_bright_dark">#000000</color> + + <!-- Wear flattens the typical 5 container layers to 3; container + high & low --> + <color name="system_surface_container_dark">#303030</color> + <color name="system_surface_variant_dark">#303030</color> + <color name="system_surface_container_high_dark">#474747</color> + <color name="system_surface_container_highest_dark">#474747</color> + <color name="system_surface_container_low_dark">#252626</color> + <color name="system_surface_container_lowest_dark">#252626</color> + </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java index 1071d728a56d..838603f80cf1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopMode.java @@ -53,4 +53,7 @@ public interface DesktopMode { /** Called when requested to go to fullscreen from the current focused desktop app. */ void moveFocusedTaskToFullscreen(int displayId); + + /** Called when requested to go to split screen from the current focused desktop app. */ + void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop); } 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 2c66fd681f62..c2c944211552 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 @@ -62,6 +62,7 @@ import com.android.wm.shell.common.SyncTransactionQueue import com.android.wm.shell.common.annotations.ExternalThread import com.android.wm.shell.common.annotations.ShellMainThread 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.desktopmode.DesktopModeTaskRepository.VisibleTasksListener import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener import com.android.wm.shell.draganddrop.DragAndDropController @@ -388,14 +389,8 @@ class DesktopTasksController( /** Enter fullscreen by moving the focused freeform task in given `displayId` to fullscreen. */ fun enterFullscreen(displayId: Int) { - if (DesktopModeStatus.isEnabled()) { - shellTaskOrganizer - .getRunningTasks(displayId) - .find { taskInfo -> - taskInfo.isFocused && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM - } - ?.let { moveToFullscreenWithAnimation(it, it.positionInParent) } - } + getFocusedFreeformTask(displayId) + ?.let { moveToFullscreenWithAnimation(it, it.positionInParent) } } /** Move a desktop app to split screen. */ @@ -876,12 +871,28 @@ class DesktopTasksController( wct.setDensityDpi(taskInfo.token, getDefaultDensityDpi()) } + /** Enter split by using the focused desktop task in given `displayId`. */ + fun enterSplit( + displayId: Int, + leftOrTop: Boolean + ) { + getFocusedFreeformTask(displayId)?.let { requestSplit(it, leftOrTop) } + } + + private fun getFocusedFreeformTask(displayId: Int): RunningTaskInfo? { + return shellTaskOrganizer.getRunningTasks(displayId) + .find { taskInfo -> taskInfo.isFocused && + taskInfo.windowingMode == WINDOWING_MODE_FREEFORM } + } + /** * Requests a task be transitioned from desktop to split select. Applies needed windowing * changes if this transition is enabled. */ + @JvmOverloads fun requestSplit( - taskInfo: RunningTaskInfo + taskInfo: RunningTaskInfo, + leftOrTop: Boolean = false, ) { val windowingMode = taskInfo.windowingMode if (windowingMode == WINDOWING_MODE_FULLSCREEN || windowingMode == WINDOWING_MODE_FREEFORM @@ -889,7 +900,8 @@ class DesktopTasksController( val wct = WindowContainerTransaction() addMoveToSplitChanges(wct, taskInfo) splitScreenController.requestEnterSplitSelect(taskInfo, wct, - SPLIT_POSITION_BOTTOM_OR_RIGHT, taskInfo.configuration.windowConfiguration.bounds) + if (leftOrTop) SPLIT_POSITION_TOP_OR_LEFT else SPLIT_POSITION_BOTTOM_OR_RIGHT, + taskInfo.configuration.windowConfiguration.bounds) } } @@ -1140,6 +1152,12 @@ class DesktopTasksController( this@DesktopTasksController.enterFullscreen(displayId) } } + + override fun moveFocusedTaskToStageSplit(displayId: Int, leftOrTop: Boolean) { + mainExecutor.execute { + this@DesktopTasksController.enterSplit(displayId, leftOrTop) + } + } } /** The interface for calls from outside the host process. */ diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index 4c8a3088a79f..92b187f90d65 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -53,6 +53,7 @@ import com.android.wm.shell.common.LaunchAdjacentController import com.android.wm.shell.common.MultiInstanceHelper import com.android.wm.shell.common.ShellExecutor import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.common.split.SplitScreenConstants import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask @@ -839,6 +840,22 @@ class DesktopTasksControllerTest : ShellTestCase() { .isEqualTo(WINDOWING_MODE_FULLSCREEN) } + fun enterSplit_freeformTaskIsMovedToSplit() { + val task1 = setUpFreeformTask() + val task2 = setUpFreeformTask() + val task3 = setUpFreeformTask() + + task1.isFocused = false + task2.isFocused = true + task3.isFocused = false + + controller.enterSplit(DEFAULT_DISPLAY, false) + + verify(splitScreenController).requestEnterSplitSelect(task2, any(), + SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT, + task2.configuration.windowConfiguration.bounds) + } + private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo { val task = createFreeformTask(displayId) whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt index b3380ff6409c..69a1a0f3196e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepositoryTest.kt @@ -17,6 +17,7 @@ package com.android.systemui.keyguard.data.repository import android.graphics.Point +import android.os.PowerManager.WAKE_REASON_UNKNOWN import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -26,8 +27,9 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.kosmos.testScope -import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest -import com.android.systemui.power.domain.interactor.powerInteractor +import com.android.systemui.power.data.repository.powerRepository +import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.power.shared.model.WakefulnessState import com.android.systemui.statusbar.CircleReveal import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.testKosmos @@ -51,7 +53,7 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val fakeKeyguardRepository = kosmos.fakeKeyguardRepository - private val powerInteractor = kosmos.powerInteractor + private val powerRepository = kosmos.powerRepository private lateinit var underTest: LightRevealScrimRepositoryImpl @get:Rule val animatorTestRule = AnimatorTestRule(this) @@ -63,7 +65,7 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { LightRevealScrimRepositoryImpl( kosmos.fakeKeyguardRepository, context, - kosmos.powerInteractor, + powerRepository, mock() ) } @@ -73,7 +75,14 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { val values = mutableListOf<LightRevealEffect>() val job = launch { underTest.revealEffect.collect { values.add(it) } } - powerInteractor.setAwakeForTest() + powerRepository.updateWakefulness( + rawState = WakefulnessState.STARTING_TO_WAKE, + lastWakeReason = WakeSleepReason.fromPowerManagerWakeReason(WAKE_REASON_UNKNOWN), + powerButtonLaunchGestureTriggered = + powerRepository.wakefulness.value.powerButtonLaunchGestureTriggered, + ) + powerRepository.updateWakefulness(rawState = WakefulnessState.AWAKE) + // We should initially emit the default reveal effect. runCurrent() values.assertEffectsMatchPredicates({ it == DEFAULT_REVEAL_EFFECT }) @@ -171,7 +180,7 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { testScope.runTest { val value by collectLastValue(underTest.revealAmount) runCurrent() - underTest.startRevealAmountAnimator(true) + underTest.startRevealAmountAnimator(true, 500L) assertEquals(0.0f, value) animatorTestRule.advanceTimeBy(500L) assertEquals(1.0f, value) @@ -183,11 +192,11 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { testScope.runTest { val value by collectLastValue(underTest.revealAmount) runCurrent() - underTest.startRevealAmountAnimator(true) + underTest.startRevealAmountAnimator(true, 500L) assertEquals(0.0f, value) animatorTestRule.advanceTimeBy(250L) assertEquals(0.5f, value) - underTest.startRevealAmountAnimator(true) + underTest.startRevealAmountAnimator(true, 500L) animatorTestRule.advanceTimeBy(250L) assertEquals(1.0f, value) } @@ -198,9 +207,9 @@ class LightRevealScrimRepositoryTest : SysuiTestCase() { testScope.runTest { val lastValue by collectLastValue(underTest.revealAmount) runCurrent() - underTest.startRevealAmountAnimator(true) + underTest.startRevealAmountAnimator(true, 500L) animatorTestRule.advanceTimeBy(500L) - underTest.startRevealAmountAnimator(false) + underTest.startRevealAmountAnimator(false, 500L) animatorTestRule.advanceTimeBy(500L) assertEquals(0.0f, lastValue) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt index 2b6e6c7c1575..3e0a1f31302d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorTest.kt @@ -24,7 +24,6 @@ import com.android.systemui.keyguard.data.repository.FakeLightRevealScrimReposit import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep -import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.testKosmos @@ -50,7 +49,6 @@ class LightRevealScrimInteractorTest : SysuiTestCase() { private val fakeLightRevealScrimRepository = kosmos.fakeLightRevealScrimRepository private val fakeKeyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository - private val testScope = kosmos.testScope private val underTest = kosmos.lightRevealScrimInteractor diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt index d9479de7e25b..eac476fcb368 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/LightRevealScrimRepository.kt @@ -26,7 +26,7 @@ import com.android.keyguard.logging.ScrimLogger import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.shared.model.BiometricUnlockModel import com.android.systemui.keyguard.shared.model.BiometricUnlockSource -import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.power.data.repository.PowerRepository import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakeSleepReason.TAP import com.android.systemui.res.R @@ -47,6 +47,7 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map val DEFAULT_REVEAL_EFFECT = LiftReveal +const val DEFAULT_REVEAL_DURATION = 500L /** * Encapsulates state relevant to the light reveal scrim, the view used to reveal/hide screen @@ -63,7 +64,9 @@ interface LightRevealScrimRepository { val revealAmount: Flow<Float> - fun startRevealAmountAnimator(reveal: Boolean) + val isAnimating: Boolean + + fun startRevealAmountAnimator(reveal: Boolean, duration: Long = DEFAULT_REVEAL_DURATION) } @SysUISingleton @@ -72,7 +75,7 @@ class LightRevealScrimRepositoryImpl constructor( keyguardRepository: KeyguardRepository, val context: Context, - powerInteractor: PowerInteractor, + powerRepository: PowerRepository, private val scrimLogger: ScrimLogger, ) : LightRevealScrimRepository { companion object { @@ -125,7 +128,7 @@ constructor( /** The reveal effect we'll use for the next non-biometric unlock (tap, power button, etc). */ private val nonBiometricRevealEffect: Flow<LightRevealEffect?> = - powerInteractor.detailedWakefulness.flatMapLatest { wakefulnessModel -> + powerRepository.wakefulness.flatMapLatest { wakefulnessModel -> when { wakefulnessModel.isAwakeOrAsleepFrom(WakeSleepReason.POWER_BUTTON) -> powerButtonRevealEffect @@ -134,7 +137,7 @@ constructor( } } - private val revealAmountAnimator = ValueAnimator.ofFloat(0f, 1f).apply { duration = 500 } + private val revealAmountAnimator = ValueAnimator.ofFloat(0f, 1f) override val revealAmount: Flow<Float> = callbackFlow { val updateListener = @@ -149,18 +152,21 @@ constructor( revealAmountAnimator.addUpdateListener(updateListener) awaitClose { revealAmountAnimator.removeUpdateListener(updateListener) } } + override val isAnimating: Boolean + get() = revealAmountAnimator.isRunning private var willBeOrIsRevealed: Boolean? = null - override fun startRevealAmountAnimator(reveal: Boolean) { + override fun startRevealAmountAnimator(reveal: Boolean, duration: Long) { if (reveal == willBeOrIsRevealed) return willBeOrIsRevealed = reveal + revealAmountAnimator.duration = duration if (reveal && !revealAmountAnimator.isRunning) { revealAmountAnimator.start() } else { revealAmountAnimator.reverse() } - scrimLogger.d(TAG, "startRevealAmountAnimator, reveal: ", reveal) + scrimLogger.d(TAG, "startRevealAmountAnimator, reveal", reveal) } override val revealEffect = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt index 8905c9e752de..2d944c694310 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt @@ -25,14 +25,13 @@ import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.shared.model.ScreenPowerState import com.android.systemui.statusbar.LightRevealEffect import com.android.systemui.util.kotlin.sample +import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch -@ExperimentalCoroutinesApi @SysUISingleton class LightRevealScrimInteractor @Inject @@ -41,7 +40,7 @@ constructor( private val lightRevealScrimRepository: LightRevealScrimRepository, @Application private val scope: CoroutineScope, private val scrimLogger: ScrimLogger, - private val powerInteractor: PowerInteractor, + private val powerInteractor: Lazy<PowerInteractor>, ) { init { listenForStartedKeyguardTransitionStep() @@ -81,31 +80,33 @@ constructor( } private fun screenIsShowingContent() = - powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_OFF && - powerInteractor.screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON + powerInteractor.get().screenPowerState.value != ScreenPowerState.SCREEN_OFF && + powerInteractor.get().screenPowerState.value != ScreenPowerState.SCREEN_TURNING_ON - companion object { + val isAnimating: Boolean + get() = lightRevealScrimRepository.isAnimating - /** - * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given - * state after the transition is complete. If false, scrim will be fully hidden. - */ - private fun willBeRevealedInState(state: KeyguardState): Boolean { - return when (state) { - KeyguardState.OFF -> false - KeyguardState.DOZING -> false - KeyguardState.AOD -> false - KeyguardState.DREAMING -> true - KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true - KeyguardState.GLANCEABLE_HUB -> true - KeyguardState.ALTERNATE_BOUNCER -> true - KeyguardState.PRIMARY_BOUNCER -> true - KeyguardState.LOCKSCREEN -> true - KeyguardState.GONE -> true - KeyguardState.OCCLUDED -> true - } + /** + * Whether the light reveal scrim will be fully revealed (revealAmount = 1.0f) in the given + * state after the transition is complete. If false, scrim will be fully hidden. + */ + private fun willBeRevealedInState(state: KeyguardState): Boolean { + return when (state) { + KeyguardState.OFF -> false + KeyguardState.DOZING -> false + KeyguardState.AOD -> false + KeyguardState.DREAMING -> true + KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true + KeyguardState.GLANCEABLE_HUB -> true + KeyguardState.ALTERNATE_BOUNCER -> true + KeyguardState.PRIMARY_BOUNCER -> true + KeyguardState.LOCKSCREEN -> true + KeyguardState.GONE -> true + KeyguardState.OCCLUDED -> true } + } + companion object { val TAG = LightRevealScrimInteractor::class.simpleName!! } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 5f8b5ddc9de6..d0ff33869a77 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -33,6 +33,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DOZING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_GOING_AWAY; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; @@ -111,6 +112,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController; import com.android.systemui.statusbar.phone.StatusBarWindowCallback; import com.android.systemui.statusbar.policy.CallbackController; import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder; +import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.sysui.ShellInterface; import dagger.Lazy; @@ -673,9 +675,13 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } @Override - public void enterStageSplitFromRunningApp(boolean leftOrTop) { + public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) { if (mOverviewProxy != null) { try { + if (DesktopModeStatus.isEnabled() && (sysUiState.getFlags() + & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0) { + return; + } mOverviewProxy.enterStageSplitFromRunningApp(leftOrTop); } catch (RemoteException e) { Log.w(TAG_OPS, "Unable to enter stage split from the current running app"); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java index bb8168335b60..4275fc6f4097 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java @@ -169,7 +169,7 @@ public class CommandQueue extends IStatusBar.Stub implements private static final int MSG_TILE_SERVICE_REQUEST_LISTENING_STATE = 68 << MSG_SHIFT; private static final int MSG_SHOW_REAR_DISPLAY_DIALOG = 69 << MSG_SHIFT; private static final int MSG_MOVE_FOCUSED_TASK_TO_FULLSCREEN = 70 << MSG_SHIFT; - private static final int MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP = 71 << MSG_SHIFT; + private static final int MSG_MOVE_FOCUSED_TASK_TO_STAGE_SPLIT = 71 << MSG_SHIFT; private static final int MSG_SHOW_MEDIA_OUTPUT_SWITCHER = 72 << MSG_SHIFT; private static final int MSG_TOGGLE_TASKBAR = 73 << MSG_SHIFT; private static final int MSG_SETTING_CHANGED = 74 << MSG_SHIFT; @@ -503,9 +503,9 @@ public class CommandQueue extends IStatusBar.Stub implements default void moveFocusedTaskToFullscreen(int displayId) {} /** - * @see IStatusBar#enterStageSplitFromRunningApp + * @see IStatusBar#moveFocusedTaskToStageSplit */ - default void enterStageSplitFromRunningApp(boolean leftOrTop) {} + default void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) {} /** * @see IStatusBar#showMediaOutputSwitcher @@ -1338,10 +1338,13 @@ public class CommandQueue extends IStatusBar.Stub implements } @Override - public void enterStageSplitFromRunningApp(boolean leftOrTop) { + public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) { synchronized (mLock) { - mHandler.obtainMessage(MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP, - leftOrTop).sendToTarget(); + SomeArgs args = SomeArgs.obtain(); + args.argi1 = displayId; + args.argi2 = leftOrTop ? 1 : 0; + mHandler.obtainMessage(MSG_MOVE_FOCUSED_TASK_TO_STAGE_SPLIT, + args).sendToTarget(); } } @@ -1907,11 +1910,15 @@ public class CommandQueue extends IStatusBar.Stub implements } break; } - case MSG_ENTER_STAGE_SPLIT_FROM_RUNNING_APP: + case MSG_MOVE_FOCUSED_TASK_TO_STAGE_SPLIT: { + args = (SomeArgs) msg.obj; + int displayId = args.argi1; + boolean leftOrTop = args.argi2 != 0; for (int i = 0; i < mCallbacks.size(); i++) { - mCallbacks.get(i).enterStageSplitFromRunningApp((Boolean) msg.obj); + mCallbacks.get(i).moveFocusedTaskToStageSplit(displayId, leftOrTop); } break; + } case MSG_SHOW_MEDIA_OUTPUT_SWITCHER: args = (SomeArgs) msg.obj; String clientPackageName = (String) args.arg1; 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 223eaf74e2e8..88374d61d240 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -32,6 +32,7 @@ import com.android.systemui.statusbar.notification.PropertyAnimator import com.android.systemui.statusbar.notification.stack.AnimationProperties import com.android.systemui.statusbar.notification.stack.StackStateAnimator import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.Flags.lightRevealMigration import com.android.app.tracing.namedRunnable import com.android.systemui.util.settings.GlobalSettings import javax.inject.Inject @@ -45,7 +46,7 @@ private const val ANIMATE_IN_KEYGUARD_DELAY = 600L /** * Duration for the light reveal portion of the animation. */ -private const val LIGHT_REVEAL_ANIMATION_DURATION = 750L +private const val LIGHT_REVEAL_ANIMATION_DURATION = 500L /** * Controller for the unlocked screen off animation, which runs when the device is going to sleep @@ -66,7 +67,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( private val notifShadeWindowControllerLazy: dagger.Lazy<NotificationShadeWindowController>, private val interactionJankMonitor: InteractionJankMonitor, private val powerManager: PowerManager, - private val handler: Handler = Handler(), + private val handler: Handler = Handler() ) : WakefulnessLifecycle.Observer, ScreenOffAnimation { private lateinit var centralSurfaces: CentralSurfaces private lateinit var shadeViewController: ShadeViewController @@ -95,6 +96,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( duration = LIGHT_REVEAL_ANIMATION_DURATION interpolator = Interpolators.LINEAR addUpdateListener { + if (lightRevealMigration()) return@addUpdateListener if (lightRevealScrim.revealEffect !is CircleReveal) { lightRevealScrim.revealAmount = it.animatedValue as Float } @@ -107,6 +109,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( } addListener(object : AnimatorListenerAdapter() { override fun onAnimationCancel(animation: Animator) { + if (lightRevealMigration()) return if (lightRevealScrim.revealEffect !is CircleReveal) { lightRevealScrim.revealAmount = 1f } @@ -371,7 +374,7 @@ class UnlockedScreenOffAnimationController @Inject constructor( * AOD UI. */ override fun isAnimationPlaying(): Boolean { - return lightRevealAnimationPlaying || aodUiAnimationPlaying + return isScreenOffLightRevealAnimationPlaying() || aodUiAnimationPlaying } override fun shouldAnimateInKeyguard(): Boolean = @@ -395,6 +398,9 @@ class UnlockedScreenOffAnimationController @Inject constructor( /** * Whether the light reveal animation is playing. The second part of the screen off animation, * where AOD animates in, might still be playing if this returns false. + * + * Note: This only refers to the specific light reveal animation that is playing during lock + * therefore LightRevealScrimInteractor.isAnimating is not the desired response. */ fun isScreenOffLightRevealAnimationPlaying(): Boolean { return lightRevealAnimationPlaying diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index 324d723207cd..7674fe988255 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -361,12 +361,14 @@ public final class WMShell implements public void enterDesktop(int displayId) { desktopMode.enterDesktop(displayId); } - }); - mCommandQueue.addCallback(new CommandQueue.Callbacks() { @Override public void moveFocusedTaskToFullscreen(int displayId) { desktopMode.moveFocusedTaskToFullscreen(displayId); } + @Override + public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) { + desktopMode.moveFocusedTaskToStageSplit(displayId, leftOrTop); + } }); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index 6a0375d55a72..3bf54a3b5498 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -31,6 +31,7 @@ import com.android.systemui.statusbar.LightRevealScrim import com.android.systemui.statusbar.NotificationShadeWindowController import com.android.systemui.statusbar.StatusBarStateControllerImpl import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.util.mockito.eq import com.android.systemui.util.settings.GlobalSettings import junit.framework.Assert.assertFalse @@ -80,6 +81,8 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { @Mock private lateinit var handler: Handler + val kosmos = testKosmos() + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -87,14 +90,14 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { context, wakefulnessLifecycle, statusBarStateController, - dagger.Lazy<KeyguardViewMediator> { keyguardViewMediator }, + { keyguardViewMediator }, keyguardStateController, - dagger.Lazy<DozeParameters> { dozeParameters }, + { dozeParameters }, globalSettings, - dagger.Lazy<NotificationShadeWindowController> { notifShadeWindowController }, + { notifShadeWindowController }, interactionJankMonitor, powerManager, - handler = handler, + handler = handler ) controller.initialize(centralSurfaces, shadeViewController, lightRevealScrim) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt index b24b95e0d3d7..f26bb83ed3f0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeLightRevealScrimRepository.kt @@ -35,7 +35,10 @@ class FakeLightRevealScrimRepository : LightRevealScrimRepository { private val _revealAmount: MutableStateFlow<Float> = MutableStateFlow(0.0f) override val revealAmount: Flow<Float> = _revealAmount - override fun startRevealAmountAnimator(reveal: Boolean) { + override val isAnimating: Boolean + get() = false + + override fun startRevealAmountAnimator(reveal: Boolean, duration: Long) { if (reveal) { _revealAmount.value = 1.0f } else { diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt index 58e0a3b32f78..d5bdbdbaa90d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractorKosmos.kt @@ -29,6 +29,6 @@ val Kosmos.lightRevealScrimInteractor by lightRevealScrimRepository, applicationCoroutineScope, scrimLogger, - powerInteractor, + { powerInteractor }, ) } diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 7afb78033fb5..13bc77296f0f 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -1246,7 +1246,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState, int flags) { - final FillResponse existingResponse = shouldRequestSecondaryProvider(flags) + boolean isSecondary = shouldRequestSecondaryProvider(flags); + final FillResponse existingResponse = isSecondary ? viewState.getSecondaryResponse() : viewState.getResponse(); mFillRequestEventLogger.startLogForNewRequest(); mRequestCount++; @@ -1283,12 +1284,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } viewState.setState(newState); - - int requestId; - // TODO(b/158623971): Update this to prevent possible overflow - do { - requestId = sIdCounter.getAndIncrement(); - } while (requestId == INVALID_REQUEST_ID); + int requestId = getRequestId(isSecondary); // Create a metrics log for the request final int ordinal = mRequestLogs.size() + 1; @@ -1367,6 +1363,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState requestAssistStructureLocked(requestId, flags); } + private static int getRequestId(boolean isSecondary) { + // For authentication flows, there needs to be a way to know whether to retrieve the Fill + // Response from the primary provider or the secondary provider from the requestId. A simple + // way to achieve this is by assigning odd number request ids to secondary provider and + // even numbers to primary provider. + int requestId; + // TODO(b/158623971): Update this to prevent possible overflow + if (isSecondary) { + do { + requestId = sIdCounter.getAndIncrement(); + } while (!isSecondaryProviderRequestId(requestId)); + } else { + do { + requestId = sIdCounter.getAndIncrement(); + } while (requestId == INVALID_REQUEST_ID || isSecondaryProviderRequestId(requestId)); + } + return requestId; + } + private boolean isRequestSupportFillDialog(int flags) { return (flags & FLAG_SUPPORTS_FILL_DIALOG) != 0; } @@ -2790,7 +2805,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState removeFromService(); return; } - final FillResponse authenticatedResponse = mResponses.get(requestId); + final FillResponse authenticatedResponse = isSecondaryProviderRequestId(requestId) + ? mSecondaryResponses.get(requestId) + : mResponses.get(requestId); if (authenticatedResponse == null || data == null) { Slog.w(TAG, "no authenticated response"); mPresentationStatsEventLogger.maybeSetAuthenticationResult( @@ -2915,6 +2932,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } + private static boolean isSecondaryProviderRequestId(int requestId) { + return requestId % 2 == 1; + } + private Dataset getDatasetFromCredentialResponse(GetCredentialResponse result) { if (result == null) { return null; diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java index 3e68920fbabe..1f8736b770e2 100644 --- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java +++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java @@ -31,6 +31,7 @@ import android.os.Build; import android.os.ParcelFileDescriptor; import android.util.Slog; +import com.android.internal.annotations.VisibleForTesting; import com.android.server.backup.utils.BackupEligibilityRules; import java.io.BufferedInputStream; @@ -62,7 +63,7 @@ public class PackageManagerBackupAgent extends BackupAgent { // key under which we store global metadata (individual app metadata // is stored using the package name as a key) - private static final String GLOBAL_METADATA_KEY = "@meta@"; + @VisibleForTesting static final String GLOBAL_METADATA_KEY = "@meta@"; // key under which we store the identity of the user's chosen default home app private static final String DEFAULT_HOME_KEY = "@home@"; @@ -72,19 +73,19 @@ public class PackageManagerBackupAgent extends BackupAgent { // ANCESTRAL_RECORD_VERSION=1 (introduced Android P). // Should the ANCESTRAL_RECORD_VERSION be bumped up in the future, STATE_FILE_VERSION will also // need bumping up, assuming more data needs saving to the state file. - private static final String STATE_FILE_HEADER = "=state="; - private static final int STATE_FILE_VERSION = 2; + @VisibleForTesting static final String STATE_FILE_HEADER = "=state="; + @VisibleForTesting static final int STATE_FILE_VERSION = 2; // key under which we store the saved ancestral-dataset format (starting from Android P) // IMPORTANT: this key needs to come first in the restore data stream (to find out // whether this version of Android knows how to restore the incoming data set), so it needs // to be always the first one in alphabetical order of all the keys - private static final String ANCESTRAL_RECORD_KEY = "@ancestral_record@"; + @VisibleForTesting static final String ANCESTRAL_RECORD_KEY = "@ancestral_record@"; // Current version of the saved ancestral-dataset format // Note that this constant was not used until Android P, and started being used // to version @pm@ data for forwards-compatibility. - private static final int ANCESTRAL_RECORD_VERSION = 1; + @VisibleForTesting static final int ANCESTRAL_RECORD_VERSION = 1; // Undefined version of the saved ancestral-dataset file format means that the restore data // is coming from pre-Android P device. @@ -593,7 +594,8 @@ public class PackageManagerBackupAgent extends BackupAgent { } // Util: write out our new backup state file - private void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) { + @VisibleForTesting + static void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) { FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor()); BufferedOutputStream outbuf = new BufferedOutputStream(outstream); DataOutputStream out = new DataOutputStream(outbuf); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index d13b28f7b122..19f0298a4a66 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -5023,6 +5023,14 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub .getSortedInputMethodAndSubtypeList( showAuxSubtypes, isScreenLocked, true /* forImeMenu */, mContext, mSettings.getMethodMap(), mSettings.getUserId()); + if (imList.isEmpty()) { + Slog.w(TAG, "Show switching menu failed, imList is empty," + + " showAuxSubtypes: " + showAuxSubtypes + + " isScreenLocked: " + isScreenLocked + + " userId: " + mSettings.getUserId()); + return false; + } + mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId, lastInputMethodId, lastInputMethodSubtypeId, imList); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java index 6ed4848c20b4..3bd0a9fa1f4b 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java @@ -80,10 +80,6 @@ final class InputMethodMenuController { final int userId = mService.getCurrentImeUserIdLocked(); - if (imList.isEmpty()) { - return; - } - hideInputMethodMenuLocked(); if (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID) { diff --git a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java index 1379d166e805..1c958a929546 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodSubtypeSwitchingController.java @@ -167,6 +167,7 @@ final class InputMethodSubtypeSwitchingController { final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodList(); if (imis.isEmpty()) { + Slog.w(TAG, "Enabled input method list is empty."); return new ArrayList<>(); } if (isScreenLocked && includeAuxiliarySubtypes) { diff --git a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java index 9caf5cfe67f3..396192e085e7 100644 --- a/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java +++ b/services/core/java/com/android/server/inputmethod/ZeroJankProxy.java @@ -101,7 +101,6 @@ public class ZeroJankProxy extends IInputMethodManager.Stub { } private void offloadInner(Runnable r) { - boolean useThrowingRunnable = r instanceof ThrowingRunnable; final long identity = Binder.clearCallingIdentity(); try { mExecutor.execute(() -> { @@ -110,14 +109,9 @@ public class ZeroJankProxy extends IInputMethodManager.Stub { Binder.restoreCallingIdentity(identity); try { try { - if (useThrowingRunnable) { - ((ThrowingRunnable) r).runOrThrow(); - } else { - r.run(); - } + r.run(); } catch (Exception e) { - Slog.e(TAG, "Error in async call", e); - throw ExceptionUtils.propagate(e); + Slog.e(TAG, "Error in async IMMS call", e); } } finally { Binder.restoreCallingIdentity(inner); diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index ec4b38b10af2..5974ac8bd41b 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3523,7 +3523,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { case KeyEvent.KEYCODE_DPAD_LEFT: if (firstDown && event.isMetaPressed()) { if (event.isCtrlPressed()) { - enterStageSplitFromRunningApp(true /* leftOrTop */); + moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event), + true /* leftOrTop */); logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION); } else { logKeyboardSystemsEvent(event, KeyboardLogEvent.BACK); @@ -3534,7 +3535,8 @@ public class PhoneWindowManager implements WindowManagerPolicy { break; case KeyEvent.KEYCODE_DPAD_RIGHT: if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) { - enterStageSplitFromRunningApp(false /* leftOrTop */); + moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event), + false /* leftOrTop */); logKeyboardSystemsEvent(event, KeyboardLogEvent.SPLIT_SCREEN_NAVIGATION); return true; } @@ -4387,10 +4389,10 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - private void enterStageSplitFromRunningApp(boolean leftOrTop) { + private void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) { StatusBarManagerInternal statusbar = getStatusBarManagerInternal(); if (statusbar != null) { - statusbar.enterStageSplitFromRunningApp(leftOrTop); + statusbar.moveFocusedTaskToStageSplit(displayId, leftOrTop); } } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index 14e0ce1704c8..c73f89c4731e 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -233,9 +233,9 @@ public interface StatusBarManagerInternal { /** * Enters stage split from a current running app. * - * @see com.android.internal.statusbar.IStatusBar#enterStageSplitFromRunningApp + * @see com.android.internal.statusbar.IStatusBar#moveFocusedTaskToStageSplit */ - void enterStageSplitFromRunningApp(boolean leftOrTop); + void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop); /** * Shows the media output switcher dialog. diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index 0b48a75298a4..214dbe01aee5 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -820,11 +820,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void enterStageSplitFromRunningApp(boolean leftOrTop) { + public void moveFocusedTaskToStageSplit(int displayId, boolean leftOrTop) { IStatusBar bar = mBar; if (bar != null) { try { - bar.enterStageSplitFromRunningApp(leftOrTop); + bar.moveFocusedTaskToStageSplit(displayId, leftOrTop); } catch (RemoteException ex) { } } } diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java index 9b19a707d7be..3fb5998d3d93 100644 --- a/services/core/java/com/android/server/wm/WallpaperController.java +++ b/services/core/java/com/android/server/wm/WallpaperController.java @@ -157,13 +157,21 @@ class WallpaperController { mFindResults.setUseTopWallpaperAsTarget(true); } - if (mService.mPolicy.isKeyguardLocked() && w.canShowWhenLocked()) { - if (mService.mPolicy.isKeyguardOccluded() || (useShellTransition - ? w.inTransition() : mService.mPolicy.isKeyguardUnoccluding())) { - // The lowest show when locked window decides whether we need to put the wallpaper - // behind. - mFindResults.mNeedsShowWhenLockedWallpaper = !isFullscreen(w.mAttrs) - || (w.mActivityRecord != null && !w.mActivityRecord.fillsParent()); + if (mService.mPolicy.isKeyguardLocked()) { + if (w.canShowWhenLocked()) { + if (mService.mPolicy.isKeyguardOccluded() || (useShellTransition + ? w.inTransition() : mService.mPolicy.isKeyguardUnoccluding())) { + // The lowest show-when-locked window decides whether to show wallpaper. + mFindResults.mNeedsShowWhenLockedWallpaper = !isFullscreen(w.mAttrs) + || (w.mActivityRecord != null && !w.mActivityRecord.fillsParent()); + } + } else if (w.hasWallpaper() && mService.mPolicy.isKeyguardHostWindow(w.mAttrs) + && w.mTransitionController.isTransitionOnDisplay(mDisplayContent)) { + // If we have no candidates at all, notification shade is allowed to be the target + // of last resort even if it has not been made visible yet. + if (DEBUG_WALLPAPER) Slog.v(TAG, "Found keyguard as wallpaper target: " + w); + mFindResults.setWallpaperTarget(w); + return false; } } @@ -200,14 +208,7 @@ class WallpaperController { private boolean isRecentsTransitionTarget(WindowState w) { if (w.mTransitionController.isShellTransitionsEnabled()) { - // Because the recents activity is invisible in background while keyguard is occluded - // (the activity window is on screen while keyguard is locked) with recents animation, - // the task animating by recents needs to be wallpaper target to make wallpaper visible. - // While for unlocked case, because recents activity will be moved to top, it can be - // the wallpaper target naturally. - return w.mActivityRecord != null && w.mAttrs.type == TYPE_BASE_APPLICATION - && mDisplayContent.isKeyguardLocked() - && w.mTransitionController.isTransientHide(w.getTask()); + return false; } // The window is either the recents activity or is in the task animating by the recents. final RecentsAnimationController controller = mService.getRecentsAnimationController(); diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java index 209107e50902..ce4126a425c6 100644 --- a/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java +++ b/services/foldables/devicestateprovider/src/com/android/server/policy/BookStyleClosedStatePredicate.java @@ -177,8 +177,11 @@ public class BookStyleClosedStatePredicate implements Predicate<FoldableDeviceSt */ private static class PostureEstimator implements SensorEventListener, Dumpable { + private static final String FLAT_INCLINATION_THRESHOLD_DEGREES_PROPERTY + = "persist.foldable_postures.wedge_inclination_threshold_degrees"; - private static final int FLAT_INCLINATION_THRESHOLD_DEGREES = 8; + private static final int FLAT_INCLINATION_THRESHOLD_DEGREES = Integer.parseInt( + System.getProperty(FLAT_INCLINATION_THRESHOLD_DEGREES_PROPERTY, "25")); /** * Alpha parameter of the accelerometer low pass filter: the lower the value, the less high diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/PackageManagerBackupAgentTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/PackageManagerBackupAgentTest.java new file mode 100644 index 000000000000..d1b6de08db51 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/backup/PackageManagerBackupAgentTest.java @@ -0,0 +1,288 @@ +/* + * 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.backup; + +import static androidx.test.core.app.ApplicationProvider.getApplicationContext; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.backup.BackupDataInput; +import android.app.backup.BackupDataOutput; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.ParcelFileDescriptor; +import android.platform.test.annotations.Presubmit; + +import androidx.test.runner.AndroidJUnit4; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; + +import java.io.BufferedOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.nio.ByteBuffer; +import java.util.Optional; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class PackageManagerBackupAgentTest { + + private static final String EXISTING_PACKAGE_NAME = "com.android.wallpaperbackup"; + private static final int USER_ID = 0; + + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + private PackageManagerBackupAgent mPackageManagerBackupAgent; + private ImmutableList<PackageInfo> mPackages; + private File mBackupData, mOldState, mNewState; + + @Before + public void setUp() throws Exception { + PackageManager packageManager = getApplicationContext().getPackageManager(); + + PackageInfo existingPackageInfo = + packageManager.getPackageInfoAsUser( + EXISTING_PACKAGE_NAME, PackageManager.GET_SIGNING_CERTIFICATES, USER_ID); + mPackages = ImmutableList.of(existingPackageInfo); + mPackageManagerBackupAgent = + new PackageManagerBackupAgent(packageManager, mPackages, USER_ID); + + mBackupData = folder.newFile("backup_data"); + mOldState = folder.newFile("old_state"); + mNewState = folder.newFile("new_state"); + } + + @Test + public void onBackup_noState_backsUpEverything() throws Exception { + // no setup needed + + runBackupAgentOnBackup(); + + // key/values should be written to backup data + ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData); + assertThat(keyValues.keySet()) + .containsExactly( + PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY, + PackageManagerBackupAgent.GLOBAL_METADATA_KEY, + EXISTING_PACKAGE_NAME) + .inOrder(); + // new state must not be empty + assertThat(mNewState.length()).isGreaterThan(0); + } + + @Test + public void onBackup_recentState_backsUpNothing() throws Exception { + try (ParcelFileDescriptor oldStateDescriptor = openForWriting(mOldState)) { + PackageManagerBackupAgent.writeStateFile(mPackages, oldStateDescriptor); + } + + runBackupAgentOnBackup(); + + // We shouldn't have written anything, but a known issue is that we always write the + // ancestral record version. + ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData); + assertThat(keyValues.keySet()) + .containsExactly(PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY); + assertThat(mNewState.length()).isGreaterThan(0); + assertThat(mNewState.length()).isEqualTo(mOldState.length()); + } + + @Test + public void onBackup_oldState_backsUpChanges() throws Exception { + String uninstalledPackageName = "does.not.exist"; + try (ParcelFileDescriptor oldStateDescriptor = openForWriting(mOldState)) { + PackageManagerBackupAgent.writeStateFile( + ImmutableList.of(createPackage(uninstalledPackageName, 1)), oldStateDescriptor); + } + + runBackupAgentOnBackup(); + + // Note that uninstalledPackageName should not exist, i.e. it did not get deleted. + ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData); + assertThat(keyValues.keySet()) + .containsExactly( + PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY, EXISTING_PACKAGE_NAME); + assertThat(mNewState.length()).isGreaterThan(0); + } + + @Test + public void onBackup_legacyState_backsUpEverything() throws Exception { + String uninstalledPackageName = "does.not.exist"; + writeLegacyStateFile( + mOldState, + ImmutableList.of(createPackage(uninstalledPackageName, 1), mPackages.getFirst())); + + runBackupAgentOnBackup(); + + ImmutableMap<String, Optional<ByteBuffer>> keyValues = getKeyValues(mBackupData); + assertThat(keyValues.keySet()) + .containsExactly( + PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY, + PackageManagerBackupAgent.GLOBAL_METADATA_KEY, + EXISTING_PACKAGE_NAME); + assertThat(mNewState.length()).isGreaterThan(0); + } + + @Test + public void onRestore_recentBackup_restoresBackup() throws Exception { + runBackupAgentOnBackup(); + + runBackupAgentOnRestore(); + + assertThat(mPackageManagerBackupAgent.getRestoredPackages()) + .containsExactly(EXISTING_PACKAGE_NAME); + // onRestore does not write to newState + assertThat(mNewState.length()).isEqualTo(0); + } + + @Test + public void onRestore_legacyBackup_restoresBackup() throws Exception { + // A legacy backup is one without an ancestral record version. Ancestral record versions + // are always written however, so we'll need to delete it from the backup data before + // restoring. + runBackupAgentOnBackup(); + deleteKeyFromBackupData(mBackupData, PackageManagerBackupAgent.ANCESTRAL_RECORD_KEY); + + runBackupAgentOnRestore(); + + assertThat(mPackageManagerBackupAgent.getRestoredPackages()) + .containsExactly(EXISTING_PACKAGE_NAME); + // onRestore does not write to newState + assertThat(mNewState.length()).isEqualTo(0); + } + + private void runBackupAgentOnBackup() throws Exception { + try (ParcelFileDescriptor oldStateDescriptor = openForReading(mOldState); + ParcelFileDescriptor backupDataDescriptor = openForWriting(mBackupData); + ParcelFileDescriptor newStateDescriptor = openForWriting(mNewState)) { + mPackageManagerBackupAgent.onBackup( + oldStateDescriptor, + new BackupDataOutput(backupDataDescriptor.getFileDescriptor()), + newStateDescriptor); + } + } + + private void runBackupAgentOnRestore() throws Exception { + try (ParcelFileDescriptor backupDataDescriptor = openForReading(mBackupData); + ParcelFileDescriptor newStateDescriptor = openForWriting(mNewState)) { + mPackageManagerBackupAgent.onRestore( + new BackupDataInput(backupDataDescriptor.getFileDescriptor()), + /* appVersionCode= */ 0, + newStateDescriptor); + } + } + + private void deleteKeyFromBackupData(File backupData, String key) throws Exception { + File temporaryBackupData = folder.newFile("backup_data.tmp"); + try (ParcelFileDescriptor inputDescriptor = openForReading(backupData); + ParcelFileDescriptor outputDescriptor = openForWriting(temporaryBackupData); ) { + BackupDataInput input = new BackupDataInput(inputDescriptor.getFileDescriptor()); + BackupDataOutput output = new BackupDataOutput(outputDescriptor.getFileDescriptor()); + while (input.readNextHeader()) { + if (input.getKey().equals(key)) { + if (input.getDataSize() > 0) { + input.skipEntityData(); + } + continue; + } + output.writeEntityHeader(input.getKey(), input.getDataSize()); + if (input.getDataSize() < 0) { + input.skipEntityData(); + } else { + byte[] buf = new byte[input.getDataSize()]; + input.readEntityData(buf, 0, buf.length); + output.writeEntityData(buf, buf.length); + } + } + } + assertThat(temporaryBackupData.renameTo(backupData)).isTrue(); + } + + private static PackageInfo createPackage(String name, int versionCode) { + PackageInfo packageInfo = new PackageInfo(); + packageInfo.packageName = name; + packageInfo.versionCodeMajor = versionCode; + return packageInfo; + } + + /** This creates a legacy state file in which {@code STATE_FILE_HEADER} was not yet present. */ + private static void writeLegacyStateFile(File stateFile, ImmutableList<PackageInfo> packages) + throws Exception { + try (ParcelFileDescriptor stateFileDescriptor = openForWriting(stateFile); + DataOutputStream out = + new DataOutputStream( + new BufferedOutputStream( + new FileOutputStream( + stateFileDescriptor.getFileDescriptor())))) { + out.writeUTF(PackageManagerBackupAgent.GLOBAL_METADATA_KEY); + out.writeInt(Build.VERSION.SDK_INT); + out.writeUTF(Build.VERSION.INCREMENTAL); + + // now write all the app names + versions + for (PackageInfo pkg : packages) { + out.writeUTF(pkg.packageName); + out.writeInt(pkg.versionCode); + } + out.flush(); + } + } + + /** + * Reads the given backup data file and returns a map of key-value pairs. The value is a {@link + * ByteBuffer} wrapped in an {@link Optional}, where the empty {@link Optional} represents a key + * deletion. + */ + private static ImmutableMap<String, Optional<ByteBuffer>> getKeyValues(File backupData) + throws Exception { + ImmutableMap.Builder<String, Optional<ByteBuffer>> builder = ImmutableMap.builder(); + try (ParcelFileDescriptor backupDataDescriptor = openForReading(backupData)) { + BackupDataInput backupDataInput = + new BackupDataInput(backupDataDescriptor.getFileDescriptor()); + while (backupDataInput.readNextHeader()) { + ByteBuffer value = null; + if (backupDataInput.getDataSize() >= 0) { + byte[] val = new byte[backupDataInput.getDataSize()]; + backupDataInput.readEntityData(val, 0, val.length); + value = ByteBuffer.wrap(val); + } + builder.put(backupDataInput.getKey(), Optional.ofNullable(value)); + } + } + return builder.build(); + } + + private static ParcelFileDescriptor openForWriting(File file) throws Exception { + return ParcelFileDescriptor.open( + file, + ParcelFileDescriptor.MODE_CREATE + | ParcelFileDescriptor.MODE_TRUNCATE + | ParcelFileDescriptor.MODE_WRITE_ONLY); + } + + private static ParcelFileDescriptor openForReading(File file) throws Exception { + return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index 38a66a9d5486..eeec54fdc99a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -411,6 +411,10 @@ public class InsetsPolicyTest extends WindowTestsBase { app2.mAboveInsetsState.addSource(statusBarSource); assertTrue(app2.getInsetsState().peekSource(statusBarId).isVisible()); + // Let app2 be the focused window. Otherwise, the control target could be overwritten by + // DisplayPolicy#updateSystemBarAttributes unexpectedly. + mDisplayContent.getDisplayPolicy().focusChangedLw(null, app2); + app2.setRequestedVisibleTypes(0, navigationBars() | statusBars()); mDisplayContent.getInsetsPolicy().updateBarControlTarget(app2); waitUntilWindowAnimatorIdle(); diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java index 80fb44a9b50f..72bedf2da21f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java @@ -39,6 +39,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assume.assumeFalse; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -305,12 +307,12 @@ public class WallpaperControllerTests extends WindowTestsBase { final WallpaperController wallpaperController = mDisplayContent.mWallpaperController; wallpaperController.adjustWallpaperWindows(); // Wallpaper is visible because the show-when-locked activity is translucent. - assertTrue(wallpaperController.isWallpaperTarget(wallpaperWindow)); + assertSame(wallpaperWindow, wallpaperController.getWallpaperTarget()); behind.mActivityRecord.setShowWhenLocked(true); wallpaperController.adjustWallpaperWindows(); // Wallpaper is invisible because the lowest show-when-locked activity is opaque. - assertTrue(wallpaperController.isWallpaperTarget(null)); + assertNull(wallpaperController.getWallpaperTarget()); // A show-when-locked wallpaper is used for lockscreen. So the top wallpaper should // be the one that is not show-when-locked. @@ -374,10 +376,10 @@ public class WallpaperControllerTests extends WindowTestsBase { // The activity in restore-below task should not be the target if keyguard is not locked. mDisplayContent.mWallpaperController.adjustWallpaperWindows(); assertNotEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget()); - // The activity in restore-below task should be the target if keyguard is occluded. + // The activity in restore-below task should not be the target if keyguard is occluded. doReturn(true).when(mDisplayContent).isKeyguardLocked(); mDisplayContent.mWallpaperController.adjustWallpaperWindows(); - assertEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget()); + assertNotEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget()); } @Test |