diff options
16 files changed, 455 insertions, 153 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java index eebd13370321..77b8663861ab 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasks.java @@ -16,6 +16,9 @@ package com.android.wm.shell.recents; +import android.annotation.Nullable; +import android.graphics.Color; + import com.android.wm.shell.shared.annotations.ExternalThread; import com.android.wm.shell.util.GroupedRecentTaskInfo; @@ -40,4 +43,12 @@ public interface RecentTasks { */ default void addAnimationStateListener(Executor listenerExecutor, Consumer<Boolean> listener) { } + + /** + * Sets a background color on the transition root layered behind the outgoing task. {@code null} + * may be used to clear any previously set colors to avoid showing a background at all. The + * color is always shown at full opacity. + */ + default void setTransitionBackgroundColor(@Nullable Color color) { + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index 0c99aed6852e..e7d9812e5393 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -30,6 +30,7 @@ import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.graphics.Color; import android.os.Bundle; import android.os.RemoteException; import android.util.Slog; @@ -476,6 +477,16 @@ public class RecentTasksController implements TaskStackListenerCallback, }); }); } + + @Override + public void setTransitionBackgroundColor(@Nullable Color color) { + mMainExecutor.execute(() -> { + if (mTransitionHandler == null) { + return; + } + mTransitionHandler.setTransitionBackgroundColor(color); + }); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 24cf3706e25a..c625b69deac0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -36,6 +36,7 @@ import android.app.ActivityTaskManager; import android.app.IApplicationThread; import android.app.PendingIntent; import android.content.Intent; +import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; @@ -56,6 +57,8 @@ import android.window.TransitionRequestInfo; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; +import androidx.annotation.NonNull; + import com.android.internal.annotations.VisibleForTesting; import com.android.internal.os.IResultReceiver; import com.android.internal.protolog.common.ProtoLog; @@ -92,6 +95,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { private final ArrayList<RecentsMixedHandler> mMixers = new ArrayList<>(); private final HomeTransitionObserver mHomeTransitionObserver; + private @Nullable Color mBackgroundColor; public RecentsTransitionHandler(ShellInit shellInit, Transitions transitions, @Nullable RecentTasksController recentTasksController, @@ -123,6 +127,15 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { mStateListeners.add(listener); } + /** + * Sets a background color on the transition root layered behind the outgoing task. {@code null} + * may be used to clear any previously set colors to avoid showing a background at all. The + * color is always shown at full opacity. + */ + public void setTransitionBackgroundColor(@Nullable Color color) { + mBackgroundColor = color; + } + @VisibleForTesting public IBinder startRecentsTransition(PendingIntent intent, Intent fillIn, Bundle options, IApplicationThread appThread, IRecentsAnimationRunner listener) { @@ -469,6 +482,16 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { final int belowLayers = info.getChanges().size(); final int middleLayers = info.getChanges().size() * 2; final int aboveLayers = info.getChanges().size() * 3; + + // Add a background color to each transition root in this transition. + if (mBackgroundColor != null) { + info.getChanges().stream() + .mapToInt((change) -> TransitionUtil.rootIndexFor(change, info)) + .distinct() + .mapToObj((rootIndex) -> info.getRoot(rootIndex).getLeash()) + .forEach((root) -> createBackgroundSurface(t, root, middleLayers)); + } + for (int i = 0; i < info.getChanges().size(); ++i) { final TransitionInfo.Change change = info.getChanges().get(i); final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo(); @@ -1107,6 +1130,29 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { return true; } + private void createBackgroundSurface(SurfaceControl.Transaction transaction, + SurfaceControl parent, int layer) { + if (mBackgroundColor == null) { + return; + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + " adding background color to layer=%d", layer); + final SurfaceControl background = new SurfaceControl.Builder() + .setName("recents_background") + .setColorLayer() + .setOpaque(true) + .setParent(parent) + .build(); + transaction.setColor(background, colorToFloatArray(mBackgroundColor)); + transaction.setLayer(background, layer); + transaction.setAlpha(background, 1F); + transaction.show(background); + } + + private static float[] colorToFloatArray(@NonNull Color color) { + return new float[]{color.red(), color.green(), color.blue()}; + } + private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct, SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) { if (!sendUserLeaveHint && task.isLeaf()) { 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 4533f58c1c37..356bfe23f6a5 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 @@ -11,6 +11,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.dimensionResource import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.ElementKey @@ -24,11 +25,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.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalTransitionKeys -import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.util.CommunalColors import com.android.systemui.res.R import com.android.systemui.scene.shared.model.SceneDataSourceDelegator import com.android.systemui.scene.ui.composable.SceneTransitionLayoutDataSource @@ -75,6 +75,7 @@ fun CommunalContainer( viewModel: CommunalViewModel, dataSourceDelegator: SceneDataSourceDelegator, dialogFactory: SystemUIDialogFactory, + colors: CommunalColors, ) { val coroutineScope = rememberCoroutineScope() val currentSceneKey: SceneKey by viewModel.currentScene.collectAsState(CommunalScenes.Blank) @@ -135,7 +136,7 @@ fun CommunalContainer( emptyMap() }, ) { - CommunalScene(viewModel, dialogFactory, modifier = modifier) + CommunalScene(viewModel, colors, dialogFactory, modifier = modifier) } } } @@ -143,15 +144,18 @@ fun CommunalContainer( /** Scene containing the glanceable hub UI. */ @Composable private fun SceneScope.CommunalScene( - viewModel: BaseCommunalViewModel, + viewModel: CommunalViewModel, + colors: CommunalColors, dialogFactory: SystemUIDialogFactory, modifier: Modifier = Modifier, ) { + val backgroundColor by colors.backgroundColor.collectAsState() + Box( modifier = Modifier.element(Communal.Elements.Scrim) .fillMaxSize() - .background(LocalAndroidColorScheme.current.outlineVariant), + .background(Color(backgroundColor.toArgb())), ) Box(modifier.element(Communal.Elements.Content)) { CommunalHub(viewModel = viewModel, dialogFactory = dialogFactory) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt new file mode 100644 index 000000000000..55e46dc1c434 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/wmshell/WMShellTest.kt @@ -0,0 +1,215 @@ +/* + * 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.wmshell + +import android.content.pm.UserInfo +import android.graphics.Color +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.keyguard.KeyguardUpdateMonitor +import com.android.keyguard.keyguardUpdateMonitor +import com.android.systemui.Flags.FLAG_COMMUNAL_HUB +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.ui.viewmodel.communalTransitionViewModel +import com.android.systemui.communal.util.fakeCommunalColors +import com.android.systemui.concurrency.fakeExecutor +import com.android.systemui.dock.DockManager +import com.android.systemui.dock.fakeDockManager +import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED +import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.keyguard.ScreenLifecycle +import com.android.systemui.keyguard.WakefulnessLifecycle +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.wakefulnessLifecycle +import com.android.systemui.kosmos.testScope +import com.android.systemui.model.SysUiState +import com.android.systemui.model.sysUiState +import com.android.systemui.notetask.NoteTaskInitializer +import com.android.systemui.settings.FakeDisplayTracker +import com.android.systemui.settings.UserTracker +import com.android.systemui.settings.userTracker +import com.android.systemui.statusbar.CommandQueue +import com.android.systemui.statusbar.commandQueue +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.statusbar.policy.configurationController +import com.android.systemui.statusbar.policy.keyguardStateController +import com.android.systemui.testKosmos +import com.android.systemui.user.data.repository.fakeUserRepository +import com.android.systemui.util.kotlin.JavaAdapter +import com.android.wm.shell.desktopmode.DesktopMode +import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener +import com.android.wm.shell.onehanded.OneHanded +import com.android.wm.shell.onehanded.OneHandedEventCallback +import com.android.wm.shell.onehanded.OneHandedTransitionCallback +import com.android.wm.shell.pip.Pip +import com.android.wm.shell.recents.RecentTasks +import com.android.wm.shell.splitscreen.SplitScreen +import com.android.wm.shell.sysui.ShellInterface +import java.util.Optional +import java.util.concurrent.Executor +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope +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.ArgumentMatchers.any +import org.mockito.Mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +/** + * Tests for [WMShell]. + * + * Build/Install/Run: atest SystemUITests:WMShellTest + */ +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class WMShellTest : SysuiTestCase() { + val kosmos = testKosmos() + val testScope = kosmos.testScope + + @Mock private lateinit var mShellInterface: ShellInterface + @Mock private lateinit var mScreenLifecycle: ScreenLifecycle + @Mock private lateinit var mPip: Pip + @Mock private lateinit var mSplitScreen: SplitScreen + @Mock private lateinit var mOneHanded: OneHanded + @Mock private lateinit var mNoteTaskInitializer: NoteTaskInitializer + @Mock private lateinit var mDesktopMode: DesktopMode + @Mock private lateinit var mRecentTasks: RecentTasks + + private val mCommandQueue: CommandQueue = kosmos.commandQueue + private val mConfigurationController: ConfigurationController = kosmos.configurationController + private val mKeyguardStateController: KeyguardStateController = kosmos.keyguardStateController + private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor = kosmos.keyguardUpdateMonitor + private val mSysUiState: SysUiState = kosmos.sysUiState + private val mWakefulnessLifecycle: WakefulnessLifecycle = kosmos.wakefulnessLifecycle + private val mUserTracker: UserTracker = kosmos.userTracker + private val mSysUiMainExecutor: Executor = kosmos.fakeExecutor + private val communalTransitionViewModel = kosmos.communalTransitionViewModel + + private lateinit var underTest: WMShell + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + val displayTracker = FakeDisplayTracker(mContext) + + kosmos.fakeUserRepository.setUserInfos(listOf(MAIN_USER_INFO)) + kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + + underTest = + WMShell( + mContext, + mShellInterface, + Optional.of(mPip), + Optional.of(mSplitScreen), + Optional.of(mOneHanded), + Optional.of(mDesktopMode), + Optional.of(mRecentTasks), + mCommandQueue, + mConfigurationController, + mKeyguardStateController, + mKeyguardUpdateMonitor, + mScreenLifecycle, + mSysUiState, + mWakefulnessLifecycle, + mUserTracker, + displayTracker, + mNoteTaskInitializer, + communalTransitionViewModel, + JavaAdapter(testScope.backgroundScope), + mSysUiMainExecutor + ) + } + + @Test + fun initPip_registersCommandQueueCallback() { + underTest.initPip(mPip) + verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks::class.java)) + } + + @Test + fun initOneHanded_registersCallbacks() { + underTest.initOneHanded(mOneHanded) + verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks::class.java)) + verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer::class.java)) + verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback::class.java)) + verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback::class.java)) + } + + @Test + fun initDesktopMode_registersListener() { + underTest.initDesktopMode(mDesktopMode) + verify(mDesktopMode) + .addVisibleTasksListener( + any(VisibleTasksListener::class.java), + any(Executor::class.java) + ) + } + + @Test + fun initRecentTasks_registersListener() { + underTest.initRecentTasks(mRecentTasks) + verify(mRecentTasks).addAnimationStateListener(any(Executor::class.java), any()) + } + + @Test + @EnableFlags(FLAG_COMMUNAL_HUB) + fun initRecentTasks_setRecentsBackgroundColorWhenCommunal() = + testScope.runTest { + val black = Color.valueOf(Color.BLACK) + kosmos.fakeCommunalColors.setBackgroundColor(black) + + kosmos.fakeKeyguardRepository.setKeyguardShowing(false) + + underTest.initRecentTasks(mRecentTasks) + runCurrent() + verify(mRecentTasks).setTransitionBackgroundColor(null) + verify(mRecentTasks, never()).setTransitionBackgroundColor(black) + + setDocked(true) + // Make communal available + kosmos.fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + kosmos.fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO) + kosmos.fakeKeyguardRepository.setKeyguardShowing(true) + + runCurrent() + + verify(mRecentTasks).setTransitionBackgroundColor(black) + } + + private fun TestScope.setDocked(docked: Boolean) { + kosmos.fakeDockManager.setIsDocked(docked) + val event = + if (docked) { + DockManager.STATE_DOCKED + } else { + DockManager.STATE_NONE + } + kosmos.fakeDockManager.setDockEvent(event) + runCurrent() + } + + private companion object { + val MAIN_USER_INFO = UserInfo(0, "primary", UserInfo.FLAG_MAIN) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt index 72dcb26b089a..27af99ef8014 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt @@ -24,6 +24,8 @@ import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryM import com.android.systemui.communal.data.repository.CommunalTutorialRepositoryModule import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.communal.util.CommunalColors +import com.android.systemui.communal.util.CommunalColorsImpl import com.android.systemui.communal.widgets.CommunalWidgetModule import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl @@ -60,6 +62,8 @@ interface CommunalModule { @Communal fun bindCommunalSceneDataSource(@Communal delegator: SceneDataSourceDelegator): SceneDataSource + @Binds fun bindCommunalColors(impl: CommunalColorsImpl): CommunalColors + companion object { @Provides @Communal diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt index bdf4e721a551..1bee83b41dbe 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt @@ -16,7 +16,9 @@ package com.android.systemui.communal.ui.viewmodel +import android.graphics.Color import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.util.CommunalColors import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -28,6 +30,7 @@ import com.android.systemui.keyguard.ui.viewmodel.LockscreenToGlanceableHubTrans import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.merge @@ -38,6 +41,7 @@ import kotlinx.coroutines.flow.merge class CommunalTransitionViewModel @Inject constructor( + communalColors: CommunalColors, glanceableHubToLockscreenTransitionViewModel: GlanceableHubToLockscreenTransitionViewModel, lockscreenToGlanceableHubTransitionViewModel: LockscreenToGlanceableHubTransitionViewModel, dreamToGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel, @@ -68,4 +72,13 @@ constructor( step.transitionState == TransitionState.FINISHED || step.transitionState == TransitionState.CANCELED } + + val recentsBackgroundColor: Flow<Color?> = + combine(showByDefault, communalColors.backgroundColor) { showByDefault, backgroundColor -> + if (showByDefault) { + backgroundColor + } else { + null + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt new file mode 100644 index 000000000000..1e04fe7b6eb0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/util/CommunalColors.kt @@ -0,0 +1,62 @@ +/* + * 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.communal.util + +import android.content.Context +import android.graphics.Color +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.Application +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn + +/** Wrapper around colors used for the communal UI. */ +interface CommunalColors { + /** The background color of the glanceable hub. */ + val backgroundColor: StateFlow<Color> +} + +@SysUISingleton +class CommunalColorsImpl +@Inject +constructor( + @Application applicationScope: CoroutineScope, + private val context: Context, + configurationInteractor: ConfigurationInteractor, +) : CommunalColors { + override val backgroundColor: StateFlow<Color> = + configurationInteractor.onAnyConfigurationChange + .map { loadBackgroundColor() } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = loadBackgroundColor() + ) + + private fun loadBackgroundColor(): Color = + Color.valueOf( + Utils.getColorAttrDefaultColor( + context, + com.android.internal.R.attr.materialColorOutlineVariant + ) + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index 9698548dd30d..54a59f30c8fd 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -38,7 +38,7 @@ import java.util.concurrent.Executor import javax.inject.Inject /** Class responsible to "glue" all note task dependencies. */ -internal class NoteTaskInitializer +class NoteTaskInitializer @Inject constructor( private val controller: NoteTaskController, @@ -138,11 +138,12 @@ constructor( * Returns a [NoteTaskEntryPoint] if an action should be taken, and null otherwise. */ private fun KeyEvent.toNoteTaskEntryPointOrNull(): NoteTaskEntryPoint? { - val entryPoint = when { - keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON - keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT - else -> null - } + val entryPoint = + when { + keyCode == KEYCODE_STYLUS_BUTTON_TAIL && isTailButtonNotesGesture() -> TAIL_BUTTON + keyCode == KEYCODE_N && isMetaPressed && isCtrlPressed -> KEYBOARD_SHORTCUT + else -> null + } debugLog { "toNoteTaskEntryPointOrNull: entryPoint=$entryPoint" } return entryPoint } @@ -164,7 +165,9 @@ constructor( // For now, trigger action immediately on UP of a single press, without waiting for // the multi-press timeout to expire. - debugLog { "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress" } + debugLog { + "isTailButtonNotesGesture: isMultiPress=$isMultiPress, isLongPress=$isLongPress" + } return !isMultiPress && !isLongPress } diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt index f418e7e0278f..8f6b9d0d19e9 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt @@ -36,6 +36,7 @@ import com.android.systemui.communal.dagger.Communal import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.ui.compose.CommunalContainer import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.util.CommunalColors import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -64,6 +65,7 @@ constructor( private val keyguardInteractor: KeyguardInteractor, private val shadeInteractor: ShadeInteractor, private val powerManager: PowerManager, + private val communalColors: CommunalColors, @Communal private val dataSourceDelegator: SceneDataSourceDelegator, ) { /** The container view for the hub. This will not be initialized until [initView] is called. */ @@ -168,6 +170,7 @@ constructor( PlatformTheme { CommunalContainer( viewModel = communalViewModel, + colors = communalColors, dataSourceDelegator = dataSourceDelegator, dialogFactory = dialogFactory, ) diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java index e48b6397457c..263ddc175647 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java @@ -43,6 +43,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.systemui.CoreStartable; +import com.android.systemui.communal.ui.viewmodel.CommunalTransitionViewModel; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.WMComponent; import com.android.systemui.dagger.qualifiers.Main; @@ -55,6 +56,7 @@ import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.kotlin.JavaAdapter; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.onehanded.OneHanded; @@ -124,6 +126,8 @@ public final class WMShell implements private final UserTracker mUserTracker; private final DisplayTracker mDisplayTracker; private final NoteTaskInitializer mNoteTaskInitializer; + private final CommunalTransitionViewModel mCommunalTransitionViewModel; + private final JavaAdapter mJavaAdapter; private final Executor mSysUiMainExecutor; // Listeners and callbacks. Note that we prefer member variable over anonymous class here to @@ -187,6 +191,8 @@ public final class WMShell implements UserTracker userTracker, DisplayTracker displayTracker, NoteTaskInitializer noteTaskInitializer, + CommunalTransitionViewModel communalTransitionViewModel, + JavaAdapter javaAdapter, @Main Executor sysUiMainExecutor) { mContext = context; mShell = shell; @@ -205,6 +211,8 @@ public final class WMShell implements mUserTracker = userTracker; mDisplayTracker = displayTracker; mNoteTaskInitializer = noteTaskInitializer; + mCommunalTransitionViewModel = communalTransitionViewModel; + mJavaAdapter = javaAdapter; mSysUiMainExecutor = sysUiMainExecutor; } @@ -381,6 +389,8 @@ public final class WMShell implements void initRecentTasks(RecentTasks recentTasks) { recentTasks.addAnimationStateListener(mSysUiMainExecutor, mCommandQueue::onRecentsAnimationStateChanged); + mJavaAdapter.alwaysCollectFlow(mCommunalTransitionViewModel.getRecentsBackgroundColor(), + recentTasks::setTransitionBackgroundColor); } @Override 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 e611da0044e6..ee03236d00b3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/GlanceableHubContainerControllerTest.kt @@ -36,6 +36,7 @@ import com.android.systemui.communal.domain.interactor.communalInteractor import com.android.systemui.communal.domain.interactor.setCommunalAvailable import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.ui.viewmodel.CommunalViewModel +import com.android.systemui.communal.util.CommunalColors import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -85,6 +86,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { @Mock private lateinit var communalViewModel: CommunalViewModel @Mock private lateinit var powerManager: PowerManager @Mock private lateinit var dialogFactory: SystemUIDialogFactory + @Mock private lateinit var communalColors: CommunalColors private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor private lateinit var shadeInteractor: ShadeInteractor private lateinit var keyguardInteractor: KeyguardInteractor @@ -116,6 +118,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { keyguardInteractor, shadeInteractor, powerManager, + communalColors, kosmos.sceneDataSourceDelegator, ) testableLooper = TestableLooper.get(this) @@ -156,6 +159,7 @@ class GlanceableHubContainerControllerTest : SysuiTestCase() { keyguardInteractor, shadeInteractor, powerManager, + communalColors, kosmos.sceneDataSourceDelegator, ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java deleted file mode 100644 index d2c8aea5988f..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java +++ /dev/null @@ -1,141 +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.wmshell; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; - -import android.test.suitebuilder.annotation.SmallTest; - -import androidx.test.runner.AndroidJUnit4; - -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.keyguard.ScreenLifecycle; -import com.android.systemui.keyguard.WakefulnessLifecycle; -import com.android.systemui.model.SysUiState; -import com.android.systemui.notetask.NoteTaskInitializer; -import com.android.systemui.settings.FakeDisplayTracker; -import com.android.systemui.settings.UserTracker; -import com.android.systemui.statusbar.CommandQueue; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.desktopmode.DesktopMode; -import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; -import com.android.wm.shell.onehanded.OneHanded; -import com.android.wm.shell.onehanded.OneHandedEventCallback; -import com.android.wm.shell.onehanded.OneHandedTransitionCallback; -import com.android.wm.shell.pip.Pip; -import com.android.wm.shell.recents.RecentTasks; -import com.android.wm.shell.splitscreen.SplitScreen; -import com.android.wm.shell.sysui.ShellInterface; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Optional; -import java.util.concurrent.Executor; - -/** - * Tests for {@link WMShell}. - * - * Build/Install/Run: - * atest SystemUITests:WMShellTest - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class WMShellTest extends SysuiTestCase { - WMShell mWMShell; - - @Mock ShellInterface mShellInterface; - @Mock CommandQueue mCommandQueue; - @Mock ConfigurationController mConfigurationController; - @Mock KeyguardStateController mKeyguardStateController; - @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @Mock ScreenLifecycle mScreenLifecycle; - @Mock SysUiState mSysUiState; - @Mock Pip mPip; - @Mock SplitScreen mSplitScreen; - @Mock OneHanded mOneHanded; - @Mock WakefulnessLifecycle mWakefulnessLifecycle; - @Mock UserTracker mUserTracker; - @Mock ShellExecutor mSysUiMainExecutor; - @Mock NoteTaskInitializer mNoteTaskInitializer; - @Mock DesktopMode mDesktopMode; - @Mock RecentTasks mRecentTasks; - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - FakeDisplayTracker displayTracker = new FakeDisplayTracker(mContext); - mWMShell = new WMShell( - mContext, - mShellInterface, - Optional.of(mPip), - Optional.of(mSplitScreen), - Optional.of(mOneHanded), - Optional.of(mDesktopMode), - Optional.of(mRecentTasks), - mCommandQueue, - mConfigurationController, - mKeyguardStateController, - mKeyguardUpdateMonitor, - mScreenLifecycle, - mSysUiState, - mWakefulnessLifecycle, - mUserTracker, - displayTracker, - mNoteTaskInitializer, - mSysUiMainExecutor - ); - } - - @Test - public void initPip_registersCommandQueueCallback() { - mWMShell.initPip(mPip); - - verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class)); - } - - @Test - public void initOneHanded_registersCallbacks() { - mWMShell.initOneHanded(mOneHanded); - - verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class)); - verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class)); - verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class)); - verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class)); - } - - @Test - public void initDesktopMode_registersListener() { - mWMShell.initDesktopMode(mDesktopMode); - verify(mDesktopMode).addVisibleTasksListener( - any(DesktopModeTaskRepository.VisibleTasksListener.class), - any(Executor.class)); - } - - @Test - public void initRecentTasks_registersListener() { - mWMShell.initRecentTasks(mRecentTasks); - verify(mRecentTasks).addAnimationStateListener(any(Executor.class), any()); - } -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt index e36ddc17e5a8..e3c218df2c53 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModelKosmos.kt @@ -17,6 +17,7 @@ package com.android.systemui.communal.ui.viewmodel import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.util.communalColors import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.keyguard.ui.viewmodel.dreamingToGlanceableHubTransitionViewModel import com.android.systemui.keyguard.ui.viewmodel.glanceableHubToDreamingTransitionViewModel @@ -37,5 +38,6 @@ val Kosmos.communalTransitionViewModel by glanceableHubToDreamTransitionViewModel = glanceableHubToDreamingTransitionViewModel, communalInteractor = communalInteractor, keyguardTransitionInteractor = keyguardTransitionInteractor, + communalColors = communalColors, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt new file mode 100644 index 000000000000..e76cf68af05b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/CommunalColorsKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.communal.util + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.communalColors: CommunalColors by Kosmos.Fixture { fakeCommunalColors } +val Kosmos.fakeCommunalColors by Kosmos.Fixture { FakeCommunalColors() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt new file mode 100644 index 000000000000..7046658728a0 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/util/FakeCommunalColors.kt @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.communal.util + +import android.graphics.Color +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeCommunalColors : CommunalColors { + private val _backgroundColor = MutableStateFlow(Color.valueOf(Color.BLACK)) + + override val backgroundColor: StateFlow<Color> + get() = _backgroundColor.asStateFlow() + + fun setBackgroundColor(color: Color) { + _backgroundColor.value = color + } +} |