Merge "Load viewer configs of groups that have logToLogcat enabled on registration" into main
diff --git a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
index 6363e9c..25e4c43 100644
--- a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
@@ -16,7 +16,8 @@
package android.libcore;
-import android.perftests.utils.BenchmarkState;
+import androidx.benchmark.BenchmarkState;
+import androidx.benchmark.junit4.BenchmarkRule;
import android.perftests.utils.PerfStatusReporter;
import androidx.test.filters.LargeTest;
@@ -34,7 +35,8 @@
@RunWith(JUnitParamsRunner.class)
@LargeTest
public class SystemArrayCopyPerfTest {
- @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+ @Rule
+ public BenchmarkRule mBenchmarkRule = new BenchmarkRule();
public static Collection<Object[]> getData() {
return Arrays.asList(
@@ -51,7 +53,7 @@
final int len = arrayLength;
char[] src = new char[len];
char[] dst = new char[len];
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
System.arraycopy(src, 0, dst, 0, len);
}
@@ -63,7 +65,7 @@
final int len = arrayLength;
byte[] src = new byte[len];
byte[] dst = new byte[len];
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
System.arraycopy(src, 0, dst, 0, len);
}
@@ -75,7 +77,7 @@
final int len = arrayLength;
short[] src = new short[len];
short[] dst = new short[len];
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
System.arraycopy(src, 0, dst, 0, len);
}
@@ -87,7 +89,7 @@
final int len = arrayLength;
int[] src = new int[len];
int[] dst = new int[len];
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
System.arraycopy(src, 0, dst, 0, len);
}
@@ -99,7 +101,7 @@
final int len = arrayLength;
long[] src = new long[len];
long[] dst = new long[len];
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
System.arraycopy(src, 0, dst, 0, len);
}
@@ -111,7 +113,7 @@
final int len = arrayLength;
float[] src = new float[len];
float[] dst = new float[len];
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
System.arraycopy(src, 0, dst, 0, len);
}
@@ -123,7 +125,7 @@
final int len = arrayLength;
double[] src = new double[len];
double[] dst = new double[len];
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
System.arraycopy(src, 0, dst, 0, len);
}
@@ -135,7 +137,7 @@
final int len = arrayLength;
boolean[] src = new boolean[len];
boolean[] dst = new boolean[len];
- BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+ final BenchmarkState state = mBenchmarkRule.getState();
while (state.keepRunning()) {
System.arraycopy(src, 0, dst, 0, len);
}
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 27eebbe..ce17d78 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -74,6 +74,7 @@
* @param locale the locale picked.
*/
void onLocaleSelected(LocaleStore.LocaleInfo locale);
+ default void onParentLocaleSelected(LocaleStore.LocaleInfo locale) {}
}
/**
@@ -292,7 +293,7 @@
mListener, locale, mTranslatedOnly /* translate only */,
mOnActionExpandListener, this.mLocalePickerCollector);
}
-
+ mListener.onParentLocaleSelected(locale);
if (selector != null) {
getFragmentManager().beginTransaction()
.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index cb20ceb..14786e1 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -16,6 +16,7 @@
package com.android.internal.protolog;
+import static android.content.Context.PROTOLOG_SERVICE;
import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STACKTRACE;
import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STRING_ARGS;
import static android.internal.perfetto.protos.ProfileCommon.InternedString.IID;
@@ -46,6 +47,8 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData;
+import android.os.RemoteException;
+import android.os.ServiceManager;
import android.os.ShellCommand;
import android.os.SystemClock;
import android.text.TextUtils;
@@ -76,6 +79,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.UUID;
@@ -103,36 +107,45 @@
private final ProtoLogViewerConfigReader mViewerConfigReader;
@Nullable
private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
+ @NonNull
private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
+ @NonNull
private final Runnable mCacheUpdater;
+ @Nullable // null when the flag android.tracing.client_side_proto_logging is not flipped
+ private final IProtoLogService mProtoLogService;
+
+ @NonNull
private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
+ @NonNull
private final Map<String, int[]> mLogLevelCounts = new ArrayMap<>();
+ @NonNull
private final Map<String, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
private final Lock mBackgroundServiceLock = new ReentrantLock();
private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
- public PerfettoProtoLogImpl(@NonNull String viewerConfigFilePath, Runnable cacheUpdater) {
- this(() -> {
- try {
- return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
- } catch (FileNotFoundException e) {
- throw new RuntimeException("Failed to load viewer config file " + viewerConfigFilePath, e);
- }
- }, cacheUpdater);
+ public PerfettoProtoLogImpl() {
+ this(null, null, null, () -> {});
}
- public PerfettoProtoLogImpl() {
- this(null, null, () -> {});
+ public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater) {
+ this(null, null, null, cacheUpdater);
}
public PerfettoProtoLogImpl(
- @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
- Runnable cacheUpdater
- ) {
- this(viewerConfigInputStreamProvider,
- new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider),
+ @NonNull String viewerConfigFilePath,
+ @NonNull Runnable cacheUpdater) {
+ this(viewerConfigFilePath,
+ null,
+ new ProtoLogViewerConfigReader(() -> {
+ try {
+ return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
+ } catch (FileNotFoundException e) {
+ throw new RuntimeException(
+ "Failed to load viewer config file " + viewerConfigFilePath, e);
+ }
+ }),
cacheUpdater);
}
@@ -140,8 +153,18 @@
public PerfettoProtoLogImpl(
@Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
@Nullable ProtoLogViewerConfigReader viewerConfigReader,
- Runnable cacheUpdater
- ) {
+ @NonNull Runnable cacheUpdater) {
+ this(null, viewerConfigInputStreamProvider, viewerConfigReader, cacheUpdater);
+ }
+
+ private PerfettoProtoLogImpl(
+ @Nullable String viewerConfigFilePath,
+ @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+ @Nullable ProtoLogViewerConfigReader viewerConfigReader,
+ @NonNull Runnable cacheUpdater) {
+ assert (viewerConfigFilePath == null || viewerConfigInputStreamProvider == null) :
+ "Only one of viewerConfigFilePath and viewerConfigInputStreamProvider can be set";
+
Producer.init(InitArguments.DEFAULTS);
DataSourceParams params =
new DataSourceParams.Builder()
@@ -153,6 +176,27 @@
this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
this.mViewerConfigReader = viewerConfigReader;
this.mCacheUpdater = cacheUpdater;
+
+ if (android.tracing.Flags.clientSideProtoLogging()) {
+ mProtoLogService =
+ IProtoLogService.Stub.asInterface(ServiceManager.getService(PROTOLOG_SERVICE));
+ Objects.requireNonNull(mProtoLogService,
+ "ServiceManager returned a null ProtoLog service");
+
+ try {
+ var args = new ProtoLogService.RegisterClientArgs();
+
+ if (viewerConfigFilePath != null) {
+ args.setViewerConfigFile(viewerConfigFilePath);
+ }
+
+ mProtoLogService.registerClient(this, args);
+ } catch (RemoteException e) {
+ throw new RuntimeException("Failed to register ProtoLog client");
+ }
+ } else {
+ mProtoLogService = null;
+ }
}
/**
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 77ca7ce..8659a8f 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -111,7 +111,7 @@
// TODO(b/353530422): Remove - temporary fix to unblock b/352290057
// In so tests the viewer config file might not exist in which we don't
// want to provide config path to the user
- sServiceInstance = new PerfettoProtoLogImpl(null, null, sCacheUpdater);
+ sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater);
} else {
sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater);
}
diff --git a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
index 282385a..341ca0e 100644
--- a/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/desktopmode/DesktopModeStatus.java
@@ -24,6 +24,8 @@
import com.android.internal.annotations.VisibleForTesting;
import com.android.window.flags.Flags;
+import java.io.PrintWriter;
+
/**
* Constants for desktop mode feature
*/
@@ -203,4 +205,19 @@
private static boolean isDeviceEligibleForDesktopMode(@NonNull Context context) {
return !enforceDeviceRestrictions() || isDesktopModeSupported(context);
}
+
+ /** Dumps DesktopModeStatus flags and configs. */
+ public static void dump(PrintWriter pw, String prefix, Context context) {
+ String innerPrefix = prefix + " ";
+ pw.print(prefix); pw.println(TAG);
+ pw.print(innerPrefix); pw.print("maxTaskLimit="); pw.println(getMaxTaskLimit(context));
+
+ pw.print(innerPrefix); pw.print("maxTaskLimit config override=");
+ pw.println(context.getResources().getInteger(
+ R.integer.config_maxDesktopWindowingActiveTasks));
+
+ SystemProperties.Handle maxTaskLimitHandle = SystemProperties.find(MAX_TASK_LIMIT_SYS_PROP);
+ pw.print(innerPrefix); pw.print("maxTaskLimit sysprop=");
+ pw.println(maxTaskLimitHandle == null ? "null" : maxTaskLimitHandle.getInt(/* def= */ -1));
+ }
}
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 ecbb1ff..456d92a 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
@@ -1528,6 +1528,7 @@
private fun dump(pw: PrintWriter, prefix: String) {
val innerPrefix = "$prefix "
pw.println("${prefix}DesktopTasksController")
+ DesktopModeStatus.dump(pw, innerPrefix, context)
taskRepository.dump(pw, innerPrefix)
}
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index cdbac33..66b9e5c 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -281,13 +281,6 @@
}
flag {
- name: "qs_new_pipeline"
- namespace: "systemui"
- description: "Use the new pipeline for Quick Settings. Should have no behavior changes."
- bug: "241772429"
-}
-
-flag {
name: "qs_new_tiles"
namespace: "systemui"
description: "Use the new tiles in the Quick Settings. Should have no behavior changes."
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
index a18b460..a5f8057 100644
--- a/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
+++ b/packages/SystemUI/compose/core/src/com/android/compose/PlatformButtons.kt
@@ -21,8 +21,7 @@
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.heightIn
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Icon
@@ -32,7 +31,6 @@
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.android.compose.theme.LocalAndroidColorScheme
@@ -42,11 +40,10 @@
modifier: Modifier = Modifier,
enabled: Boolean = true,
colors: ButtonColors = filledButtonColors(),
- verticalPadding: Dp = DefaultPlatformButtonVerticalPadding,
content: @Composable RowScope.() -> Unit,
) {
androidx.compose.material3.Button(
- modifier = modifier.padding(vertical = verticalPadding).height(36.dp),
+ modifier = modifier.heightIn(min = 36.dp),
colors = colors,
contentPadding = ButtonPaddings,
onClick = onClick,
@@ -63,11 +60,10 @@
enabled: Boolean = true,
colors: ButtonColors = outlineButtonColors(),
border: BorderStroke? = outlineButtonBorder(),
- verticalPadding: Dp = DefaultPlatformButtonVerticalPadding,
content: @Composable RowScope.() -> Unit,
) {
androidx.compose.material3.OutlinedButton(
- modifier = modifier.padding(vertical = verticalPadding).height(36.dp),
+ modifier = modifier.heightIn(min = 36.dp),
enabled = enabled,
colors = colors,
border = border,
@@ -118,7 +114,6 @@
}
}
-private val DefaultPlatformButtonVerticalPadding = 6.dp
private val ButtonPaddings = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
@Composable
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index f655ac1..d164eab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -95,7 +95,7 @@
import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.BouncerMessageViewModel
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
import com.android.systemui.bouncer.ui.viewmodel.MessageViewModel
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
@@ -114,7 +114,7 @@
@Composable
fun BouncerContent(
- viewModel: BouncerViewModel,
+ viewModel: BouncerSceneContentViewModel,
dialogFactory: BouncerDialogFactory,
modifier: Modifier = Modifier,
) {
@@ -128,7 +128,7 @@
@VisibleForTesting
fun BouncerContent(
layout: BouncerSceneLayout,
- viewModel: BouncerViewModel,
+ viewModel: BouncerSceneContentViewModel,
dialogFactory: BouncerDialogFactory,
modifier: Modifier
) {
@@ -173,7 +173,7 @@
*/
@Composable
private fun StandardLayout(
- viewModel: BouncerViewModel,
+ viewModel: BouncerSceneContentViewModel,
modifier: Modifier = Modifier,
) {
val isHeightExpanded =
@@ -235,7 +235,7 @@
*/
@Composable
private fun SplitLayout(
- viewModel: BouncerViewModel,
+ viewModel: BouncerSceneContentViewModel,
modifier: Modifier = Modifier,
) {
val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle()
@@ -326,7 +326,7 @@
*/
@Composable
private fun BesideUserSwitcherLayout(
- viewModel: BouncerViewModel,
+ viewModel: BouncerSceneContentViewModel,
modifier: Modifier = Modifier,
) {
val layoutDirection = LocalLayoutDirection.current
@@ -461,7 +461,7 @@
/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
@Composable
private fun BelowUserSwitcherLayout(
- viewModel: BouncerViewModel,
+ viewModel: BouncerSceneContentViewModel,
modifier: Modifier = Modifier,
) {
Column(
@@ -506,7 +506,7 @@
@Composable
private fun FoldAware(
- viewModel: BouncerViewModel,
+ viewModel: BouncerSceneContentViewModel,
aboveFold: @Composable BoxScope.() -> Unit,
belowFold: @Composable BoxScope.() -> Unit,
modifier: Modifier = Modifier,
@@ -649,7 +649,7 @@
*/
@Composable
private fun OutputArea(
- viewModel: BouncerViewModel,
+ viewModel: BouncerSceneContentViewModel,
modifier: Modifier = Modifier,
) {
val authMethodViewModel: AuthMethodBouncerViewModel? by
@@ -677,7 +677,7 @@
*/
@Composable
private fun InputArea(
- viewModel: BouncerViewModel,
+ viewModel: BouncerSceneContentViewModel,
pinButtonRowVerticalSpacing: Dp,
centerPatternDotsVertically: Boolean,
modifier: Modifier = Modifier,
@@ -706,7 +706,7 @@
@Composable
private fun ActionArea(
- viewModel: BouncerViewModel,
+ viewModel: BouncerSceneContentViewModel,
modifier: Modifier = Modifier,
) {
val actionButton: BouncerActionButtonModel? by
@@ -774,7 +774,7 @@
@Composable
private fun Dialog(
- bouncerViewModel: BouncerViewModel,
+ bouncerViewModel: BouncerSceneContentViewModel,
dialogFactory: BouncerDialogFactory,
) {
val dialogViewModel by bouncerViewModel.dialogViewModel.collectAsStateWithLifecycle()
@@ -803,7 +803,7 @@
/** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */
@Composable
private fun UserSwitcher(
- viewModel: BouncerViewModel,
+ viewModel: BouncerSceneContentViewModel,
modifier: Modifier = Modifier,
) {
if (!viewModel.isUserSwitcherVisible) {
@@ -884,7 +884,7 @@
@Composable
private fun UserSwitcherDropdownMenu(
isExpanded: Boolean,
- items: List<BouncerViewModel.UserSwitcherDropdownItemViewModel>,
+ items: List<BouncerSceneContentViewModel.UserSwitcherDropdownItemViewModel>,
onDismissed: () -> Unit,
) {
val context = LocalContext.current
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 9fd30b4..3a46882 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -27,9 +27,11 @@
import com.android.compose.animation.scene.UserAction
import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneActionsViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
import com.android.systemui.compose.modifiers.sysuiResTag
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.scene.shared.model.Scenes
import com.android.systemui.scene.ui.composable.ComposableScene
import javax.inject.Inject
@@ -51,23 +53,37 @@
class BouncerScene
@Inject
constructor(
- private val viewModel: BouncerViewModel,
+ private val actionsViewModelFactory: BouncerSceneActionsViewModel.Factory,
+ private val contentViewModelFactory: BouncerSceneContentViewModel.Factory,
private val dialogFactory: BouncerDialogFactory,
) : ComposableScene {
override val key = Scenes.Bouncer
+ private val actionsViewModel: BouncerSceneActionsViewModel by lazy {
+ actionsViewModelFactory.create()
+ }
+
override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- viewModel.destinationScenes
+ actionsViewModel.actions
+
+ override suspend fun activate() {
+ actionsViewModel.activate()
+ }
@Composable
override fun SceneScope.Content(
modifier: Modifier,
- ) = BouncerScene(viewModel, dialogFactory, modifier)
+ ) =
+ BouncerScene(
+ viewModel = rememberViewModel { contentViewModelFactory.create() },
+ dialogFactory = dialogFactory,
+ modifier = modifier,
+ )
}
@Composable
private fun SceneScope.BouncerScene(
- viewModel: BouncerViewModel,
+ viewModel: BouncerSceneContentViewModel,
dialogFactory: BouncerDialogFactory,
modifier: Modifier = Modifier,
) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index c9fa671..deef652 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -22,14 +22,14 @@
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.testKosmos
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -39,17 +39,16 @@
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
- private val underTest by lazy {
- PinBouncerViewModel(
- applicationContext = context,
- viewModelScope = testScope.backgroundScope,
- interactor = bouncerInteractor,
+ private val underTest =
+ kosmos.pinBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true),
- simBouncerInteractor = kosmos.simBouncerInteractor,
- authenticationMethod = AuthenticationMethodModel.Pin,
onIntentionalUserInput = {},
+ authenticationMethod = AuthenticationMethodModel.Pin,
)
+
+ @Before
+ fun setUp() {
+ underTest.activateIn(testScope)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
index 4f5d0e5..b83ab7e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
@@ -52,6 +52,7 @@
import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.testKosmos
import com.android.systemui.user.data.repository.fakeUserRepository
@@ -87,6 +88,7 @@
intArrayOf(ignoreHelpMessageId)
)
underTest = kosmos.bouncerMessageViewModel
+ underTest.activateIn(testScope)
overrideResource(R.string.kg_trust_agent_disabled, "Trust agent is unavailable")
kosmos.fakeSystemPropertiesHelper.set(
DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt
new file mode 100644
index 0000000..a86a0c0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.testKosmos
+import com.android.systemui.truth.containsEntriesExactly
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
+class BouncerSceneActionsViewModelTest : SysuiTestCase() {
+
+ private val kosmos = testKosmos()
+ private val testScope = kosmos.testScope
+
+ private lateinit var underTest: BouncerSceneActionsViewModel
+
+ @Before
+ fun setUp() {
+ kosmos.sceneContainerStartable.start()
+ underTest = kosmos.bouncerSceneActionsViewModel
+ underTest.activateIn(testScope)
+ }
+
+ @Test
+ fun actions() =
+ testScope.runTest {
+ val actions by collectLastValue(underTest.actions)
+ kosmos.fakeSceneDataSource.changeScene(Scenes.QuickSettings)
+ runCurrent()
+
+ kosmos.fakeSceneDataSource.changeScene(Scenes.Bouncer)
+ runCurrent()
+
+ assertThat(actions)
+ .containsEntriesExactly(
+ Back to UserActionResult(Scenes.QuickSettings),
+ Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings),
+ )
+ }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
similarity index 88%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
index ccddc9c..9bddcd2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
@@ -18,10 +18,6 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserActionResult
import com.android.systemui.SysuiTestCase
import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -38,11 +34,9 @@
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.scene.domain.startable.sceneContainerStartable
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.fakeSceneDataSource
import com.android.systemui.testKosmos
-import com.android.systemui.truth.containsEntriesExactly
import com.google.common.truth.Truth.assertThat
import com.google.common.truth.Truth.assertWithMessage
import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -62,17 +56,18 @@
@SmallTest
@RunWith(AndroidJUnit4::class)
@EnableSceneContainer
-class BouncerViewModelTest : SysuiTestCase() {
+class BouncerSceneContentViewModelTest : SysuiTestCase() {
private val kosmos = testKosmos()
private val testScope = kosmos.testScope
- private lateinit var underTest: BouncerViewModel
+ private lateinit var underTest: BouncerSceneContentViewModel
@Before
fun setUp() {
kosmos.sceneContainerStartable.start()
- underTest = kosmos.bouncerViewModel
+ underTest = kosmos.bouncerSceneContentViewModel
+ underTest.activateIn(testScope)
}
@Test
@@ -201,23 +196,6 @@
assertThat(isFoldSplitRequired).isTrue()
}
- @Test
- fun destinationScenes() =
- testScope.runTest {
- val destinationScenes by collectLastValue(underTest.destinationScenes)
- kosmos.fakeSceneDataSource.changeScene(Scenes.QuickSettings)
- runCurrent()
-
- kosmos.fakeSceneDataSource.changeScene(Scenes.Bouncer)
- runCurrent()
-
- assertThat(destinationScenes)
- .containsEntriesExactly(
- Back to UserActionResult(Scenes.QuickSettings),
- Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings),
- )
- }
-
private fun authMethodsToTest(): List<AuthenticationMethodModel> {
return listOf(None, Pin, Password, Pattern, Sim)
}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index a09189e..492543f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -31,6 +31,7 @@
import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository
import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -44,7 +45,6 @@
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceTimeBy
import kotlinx.coroutines.test.runCurrent
@@ -68,12 +68,8 @@
private val isInputEnabled = MutableStateFlow(true)
private val underTest =
- PasswordBouncerViewModel(
- viewModelScope = testScope.backgroundScope,
- isInputEnabled = isInputEnabled.asStateFlow(),
- interactor = bouncerInteractor,
- inputMethodInteractor = inputMethodInteractor,
- selectedUserInteractor = selectedUserInteractor,
+ kosmos.passwordBouncerViewModelFactory.create(
+ isInputEnabled = isInputEnabled,
onIntentionalUserInput = {},
)
@@ -81,6 +77,7 @@
fun setUp() {
overrideResource(R.string.keyguard_enter_your_password, ENTER_YOUR_PASSWORD)
overrideResource(R.string.kg_wrong_password, WRONG_PASSWORD)
+ underTest.activateIn(testScope)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 14d3634..7c773a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -26,9 +26,9 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -54,17 +54,12 @@
private val testScope = kosmos.testScope
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
private val sceneInteractor by lazy { kosmos.sceneInteractor }
- private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
- private val bouncerViewModel by lazy { kosmos.bouncerViewModel }
- private val underTest by lazy {
- PatternBouncerViewModel(
- applicationContext = context,
- viewModelScope = testScope.backgroundScope,
- interactor = bouncerInteractor,
+ private val bouncerViewModel by lazy { kosmos.bouncerSceneContentViewModel }
+ private val underTest =
+ kosmos.patternBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true).asStateFlow(),
onIntentionalUserInput = {},
)
- }
private val containerSize = 90 // px
private val dotSize = 30 // px
@@ -73,6 +68,7 @@
fun setUp() {
overrideResource(R.string.keyguard_enter_your_pattern, ENTER_YOUR_PATTERN)
overrideResource(R.string.kg_wrong_pattern, WRONG_PATTERN)
+ underTest.activateIn(testScope)
}
@Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 89bafb9..8d82e97 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -31,10 +31,9 @@
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.res.R
import com.android.systemui.scene.domain.interactor.sceneInteractor
import com.android.systemui.scene.shared.model.Scenes
@@ -44,7 +43,6 @@
import kotlin.random.nextInt
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
@@ -62,24 +60,18 @@
private val testScope = kosmos.testScope
private val sceneInteractor by lazy { kosmos.sceneInteractor }
private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
- private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
- private lateinit var underTest: PinBouncerViewModel
+ private val underTest =
+ kosmos.pinBouncerViewModelFactory.create(
+ isInputEnabled = MutableStateFlow(true),
+ onIntentionalUserInput = {},
+ authenticationMethod = AuthenticationMethodModel.Pin,
+ )
@Before
fun setUp() {
- underTest =
- PinBouncerViewModel(
- applicationContext = context,
- viewModelScope = testScope.backgroundScope,
- interactor = bouncerInteractor,
- isInputEnabled = MutableStateFlow(true).asStateFlow(),
- simBouncerInteractor = kosmos.simBouncerInteractor,
- authenticationMethod = AuthenticationMethodModel.Pin,
- onIntentionalUserInput = {},
- )
-
overrideResource(R.string.keyguard_enter_your_pin, ENTER_YOUR_PIN)
overrideResource(R.string.kg_wrong_pin, WRONG_PIN)
+ underTest.activateIn(testScope)
}
@Test
@@ -96,14 +88,10 @@
fun simBouncerViewModel_simAreaIsVisible() =
testScope.runTest {
val underTest =
- PinBouncerViewModel(
- applicationContext = context,
- viewModelScope = testScope.backgroundScope,
- interactor = bouncerInteractor,
- isInputEnabled = MutableStateFlow(true).asStateFlow(),
- simBouncerInteractor = kosmos.simBouncerInteractor,
- authenticationMethod = AuthenticationMethodModel.Sim,
+ kosmos.pinBouncerViewModelFactory.create(
+ isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
+ authenticationMethod = AuthenticationMethodModel.Sim,
)
assertThat(underTest.isSimAreaVisible).isTrue()
@@ -125,14 +113,10 @@
fun simBouncerViewModel_autoConfirmEnabled_hintedPinLengthIsNull() =
testScope.runTest {
val underTest =
- PinBouncerViewModel(
- applicationContext = context,
- viewModelScope = testScope.backgroundScope,
- interactor = bouncerInteractor,
- isInputEnabled = MutableStateFlow(true).asStateFlow(),
- simBouncerInteractor = kosmos.simBouncerInteractor,
- authenticationMethod = AuthenticationMethodModel.Sim,
+ kosmos.pinBouncerViewModelFactory.create(
+ isInputEnabled = MutableStateFlow(true),
onIntentionalUserInput = {},
+ authenticationMethod = AuthenticationMethodModel.Pin,
)
kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
val hintedPinLength by collectLastValue(underTest.hintedPinLength)
@@ -355,6 +339,7 @@
AuthenticationMethodModel.Pin
)
kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
+ runCurrent()
underTest.onPinButtonClicked(1)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 3146318..8995f46 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -24,7 +24,6 @@
import android.service.quicksettings.Tile
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE
import com.android.systemui.Flags.FLAG_QS_NEW_TILES
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
@@ -107,7 +106,6 @@
fun setup() {
MockitoAnnotations.initMocks(this)
- mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
mSetFlagsRule.enableFlags(FLAG_QS_NEW_TILES)
userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
index e8ad038..00c7204 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
@@ -20,7 +20,6 @@
import android.content.pm.UserInfo
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
-import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
@@ -98,8 +97,6 @@
@Before
fun setUp() {
- mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE)
-
with(kosmos) {
restoreReconciliationInteractor.start()
autoAddInteractor.init(kosmos.currentTilesInteractor)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
index dffd0d7..6bcaea4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
@@ -20,7 +20,6 @@
import android.os.UserManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
-import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE
import com.android.systemui.SysuiTestCase
import com.android.systemui.coroutines.collectLastValue
import com.android.systemui.kosmos.Kosmos
@@ -59,6 +58,7 @@
// Getter here so it can change when there is a managed profile.
private val workTileAvailable: Boolean
get() = hasManagedProfile()
+
private val currentUser: Int
get() = kosmos.userTracker.userId
@@ -67,8 +67,6 @@
@Before
fun setUp() {
- mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
-
kosmos.qsTileFactory = FakeQSFactory(::tileCreator)
kosmos.restoreReconciliationInteractor.start()
kosmos.autoAddInteractor.init(kosmos.currentTilesInteractor)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 66e45ab..cd84abc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -36,10 +36,10 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
-import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModel
import com.android.systemui.classifier.domain.interactor.falsingInteractor
import com.android.systemui.communal.domain.interactor.communalInteractor
import com.android.systemui.coroutines.collectLastValue
@@ -139,7 +139,7 @@
private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
- private lateinit var bouncerViewModel: BouncerViewModel
+ private lateinit var bouncerSceneContentViewModel: BouncerSceneContentViewModel
private val lockscreenSceneActionsViewModel by lazy {
LockscreenSceneActionsViewModel(
@@ -187,7 +187,7 @@
}
bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
- bouncerViewModel = kosmos.bouncerViewModel
+ bouncerSceneContentViewModel = kosmos.bouncerSceneContentViewModel
shadeSceneContentViewModel = kosmos.shadeSceneContentViewModel
shadeSceneActionsViewModel = kosmos.shadeSceneActionsViewModel
@@ -198,6 +198,7 @@
lockscreenSceneActionsViewModel.activateIn(testScope)
shadeSceneContentViewModel.activateIn(testScope)
shadeSceneActionsViewModel.activateIn(testScope)
+ bouncerSceneContentViewModel.activateIn(testScope)
assertWithMessage("Initial scene key mismatch!")
.that(sceneContainerViewModel.currentScene.value)
@@ -397,7 +398,7 @@
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
- val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton)
+ val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
assertWithMessage("Bouncer action button not visible")
.that(bouncerActionButton)
.isNotNull()
@@ -417,7 +418,7 @@
assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
emulateUserDrivenTransition(to = upDestinationSceneKey)
- val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton)
+ val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
assertWithMessage("Bouncer action button not visible during call")
.that(bouncerActionButton)
.isNotNull()
@@ -568,7 +569,7 @@
bouncerSceneJob =
if (to == Scenes.Bouncer) {
testScope.backgroundScope.launch {
- bouncerViewModel.authMethodViewModel.collect {
+ bouncerSceneContentViewModel.authMethodViewModel.collect {
// Do nothing. Need this to turn this otherwise cold flow, hot.
}
}
@@ -644,7 +645,8 @@
assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
.that(getCurrentSceneInUi())
.isEqualTo(Scenes.Bouncer)
- val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel)
+ val authMethodViewModel by
+ collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
.that(authMethodViewModel)
.isInstanceOf(PinBouncerViewModel::class.java)
@@ -672,7 +674,8 @@
assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
.that(getCurrentSceneInUi())
.isEqualTo(Scenes.Bouncer)
- val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel)
+ val authMethodViewModel by
+ collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
.that(authMethodViewModel)
.isInstanceOf(PinBouncerViewModel::class.java)
@@ -719,7 +722,7 @@
/** Emulates the dismissal of the IME (soft keyboard). */
private fun TestScope.dismissIme() {
- (bouncerViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let {
+ (bouncerSceneContentViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let {
it.onImeDismissed()
runCurrent()
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
index aebc50f..3410782 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
@@ -18,8 +18,6 @@
import android.app.AlertDialog
import android.content.Context
-import com.android.systemui.bouncer.ui.viewmodel.BouncerMessageViewModelModule
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModelModule
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -27,13 +25,7 @@
import dagger.Module
import dagger.Provides
-@Module(
- includes =
- [
- BouncerViewModelModule::class,
- BouncerMessageViewModelModule::class,
- ],
-)
+@Module
interface BouncerViewModule {
/** Binds BouncerView to BouncerViewImpl and makes it injectable. */
@Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
index 78811a9..ad93a25 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -9,7 +9,7 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
@@ -40,7 +40,7 @@
@Inject
constructor(
val legacyInteractor: PrimaryBouncerInteractor,
- val viewModel: BouncerViewModel,
+ val viewModelFactory: BouncerSceneContentViewModel.Factory,
val dialogFactory: BouncerDialogFactory,
val authenticationInteractor: AuthenticationInteractor,
val viewMediatorCallback: ViewMediatorCallback?,
@@ -65,7 +65,7 @@
ComposeBouncerViewBinder.bind(
view,
deps.legacyInteractor,
- deps.viewModel,
+ deps.viewModelFactory,
deps.dialogFactory,
deps.authenticationInteractor,
deps.selectedUserInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
index eaca276..c1f7d59 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -14,7 +14,8 @@
import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.composable.BouncerContent
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.lifecycle.repeatWhenAttached
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import kotlinx.coroutines.flow.collectLatest
@@ -25,7 +26,7 @@
fun bind(
view: ViewGroup,
legacyInteractor: PrimaryBouncerInteractor,
- viewModel: BouncerViewModel,
+ viewModelFactory: BouncerSceneContentViewModel.Factory,
dialogFactory: BouncerDialogFactory,
authenticationInteractor: AuthenticationInteractor,
selectedUserInteractor: SelectedUserInteractor,
@@ -48,7 +49,14 @@
this@repeatWhenAttached.lifecycle
}
)
- setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+ setContent {
+ PlatformTheme {
+ BouncerContent(
+ rememberViewModel { viewModelFactory.create() },
+ dialogFactory,
+ )
+ }
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index 4fbf735..e7dd974 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -17,17 +17,18 @@
package com.android.systemui.bouncer.ui.viewmodel
import android.annotation.StringRes
+import com.android.app.tracing.coroutines.flow.collectLatest
import com.android.systemui.authentication.domain.interactor.AuthenticationResult
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import kotlinx.coroutines.CoroutineScope
+import com.android.systemui.lifecycle.SysUiViewModel
+import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.receiveAsFlow
sealed class AuthMethodBouncerViewModel(
- protected val viewModelScope: CoroutineScope,
protected val interactor: BouncerInteractor,
/**
@@ -37,7 +38,7 @@
* being able to attempt to unlock the device.
*/
val isInputEnabled: StateFlow<Boolean>,
-) {
+) : SysUiViewModel() {
private val _animateFailure = MutableStateFlow(false)
/**
@@ -57,6 +58,29 @@
*/
@get:StringRes abstract val lockoutMessageId: Int
+ private val authenticationRequests = Channel<AuthenticationRequest>(Channel.BUFFERED)
+
+ override suspend fun onActivated() {
+ authenticationRequests.receiveAsFlow().collectLatest { request ->
+ if (!isInputEnabled.value) {
+ return@collectLatest
+ }
+
+ val authenticationResult =
+ interactor.authenticate(
+ input = request.input,
+ tryAutoConfirm = request.useAutoConfirm,
+ )
+
+ if (authenticationResult == AuthenticationResult.SKIPPED && request.useAutoConfirm) {
+ return@collectLatest
+ }
+
+ _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
+ clearInput()
+ }
+ }
+
/**
* Notifies that the UI has been hidden from the user (after any transitions have completed).
*/
@@ -92,14 +116,11 @@
input: List<Any> = getInput(),
useAutoConfirm: Boolean = false,
) {
- viewModelScope.launch {
- val authenticationResult = interactor.authenticate(input, useAutoConfirm)
- if (authenticationResult == AuthenticationResult.SKIPPED && useAutoConfirm) {
- return@launch
- }
- _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
-
- clearInput()
- }
+ authenticationRequests.trySend(AuthenticationRequest(input, useAutoConfirm))
}
+
+ private data class AuthenticationRequest(
+ val input: List<Any>,
+ val useAutoConfirm: Boolean,
+ )
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index 31479f1..c3215b4 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -27,7 +27,6 @@
import com.android.systemui.bouncer.shared.model.BouncerMessageStrings
import com.android.systemui.bouncer.shared.model.primaryMessage
import com.android.systemui.bouncer.shared.model.secondaryMessage
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor
import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
@@ -39,19 +38,19 @@
import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage
import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
+import com.android.systemui.lifecycle.SysUiViewModel
import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
import com.android.systemui.util.kotlin.Utils.Companion.sample
import com.android.systemui.util.time.SystemClock
-import dagger.Module
-import dagger.Provides
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlin.math.ceil
import kotlin.math.max
import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -65,20 +64,21 @@
/** Holds UI state for the 2-line status message shown on the bouncer. */
@OptIn(ExperimentalCoroutinesApi::class)
-class BouncerMessageViewModel(
+class BouncerMessageViewModel
+@AssistedInject
+constructor(
@Application private val applicationContext: Context,
- @Application private val applicationScope: CoroutineScope,
private val bouncerInteractor: BouncerInteractor,
private val simBouncerInteractor: SimBouncerInteractor,
private val authenticationInteractor: AuthenticationInteractor,
- selectedUser: Flow<UserViewModel>,
+ private val userSwitcherViewModel: UserSwitcherViewModel,
private val clock: SystemClock,
private val biometricMessageInteractor: BiometricMessageInteractor,
private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
private val deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
- flags: ComposeBouncerFlags,
-) {
+ private val flags: ComposeBouncerFlags,
+) : SysUiViewModel() {
/**
* A message shown when the user has attempted the wrong credential too many times and now must
* wait a while before attempting to authenticate again.
@@ -94,6 +94,25 @@
/** The user-facing message to show in the bouncer. */
val message: MutableStateFlow<MessageViewModel?> = MutableStateFlow(null)
+ override suspend fun onActivated() {
+ if (!flags.isComposeBouncerOrSceneContainerEnabled()) {
+ return
+ }
+
+ coroutineScope {
+ launch {
+ // Update the lockout countdown whenever the selected user is switched.
+ userSwitcherViewModel.selectedUser.collect { startLockoutCountdown() }
+ }
+
+ launch { defaultBouncerMessageInitializer() }
+ launch { listenForSimBouncerEvents() }
+ launch { listenForBouncerEvents() }
+ launch { listenForFaceMessages() }
+ launch { listenForFingerprintMessages() }
+ }
+ }
+
/** Initializes the bouncer message to default whenever it is shown. */
fun onShown() {
showDefaultMessage()
@@ -108,173 +127,161 @@
private var lockoutCountdownJob: Job? = null
- private fun defaultBouncerMessageInitializer() {
- applicationScope.launch {
- resetToDefault.emit(Unit)
- authenticationInteractor.authenticationMethod
- .flatMapLatest { authMethod ->
- if (authMethod == AuthenticationMethodModel.Sim) {
- resetToDefault.map {
- MessageViewModel(simBouncerInteractor.getDefaultMessage())
- }
- } else if (authMethod.isSecure) {
- combine(
- deviceUnlockedInteractor.deviceEntryRestrictionReason,
- lockoutMessage,
- deviceEntryBiometricsAllowedInteractor
- .isFingerprintCurrentlyAllowedOnBouncer,
- resetToDefault,
- ) { deviceEntryRestrictedReason, lockoutMsg, isFpAllowedInBouncer, _ ->
- lockoutMsg
- ?: deviceEntryRestrictedReason.toMessage(
- authMethod,
- isFpAllowedInBouncer
- )
- }
- } else {
- emptyFlow()
+ private suspend fun defaultBouncerMessageInitializer() {
+ resetToDefault.emit(Unit)
+ authenticationInteractor.authenticationMethod
+ .flatMapLatest { authMethod ->
+ if (authMethod == AuthenticationMethodModel.Sim) {
+ resetToDefault.map {
+ MessageViewModel(simBouncerInteractor.getDefaultMessage())
}
+ } else if (authMethod.isSecure) {
+ combine(
+ deviceUnlockedInteractor.deviceEntryRestrictionReason,
+ lockoutMessage,
+ deviceEntryBiometricsAllowedInteractor
+ .isFingerprintCurrentlyAllowedOnBouncer,
+ resetToDefault,
+ ) { deviceEntryRestrictedReason, lockoutMsg, isFpAllowedInBouncer, _ ->
+ lockoutMsg
+ ?: deviceEntryRestrictedReason.toMessage(
+ authMethod,
+ isFpAllowedInBouncer
+ )
+ }
+ } else {
+ emptyFlow()
}
- .collectLatest { messageViewModel -> message.value = messageViewModel }
- }
+ }
+ .collectLatest { messageViewModel -> message.value = messageViewModel }
}
- private fun listenForSimBouncerEvents() {
+ private suspend fun listenForSimBouncerEvents() {
// Listen for any events from the SIM bouncer and update the message shown on the bouncer.
- applicationScope.launch {
- authenticationInteractor.authenticationMethod
- .flatMapLatest { authMethod ->
- if (authMethod == AuthenticationMethodModel.Sim) {
- simBouncerInteractor.bouncerMessageChanged.map { simMsg ->
- simMsg?.let { MessageViewModel(it) }
- }
- } else {
- emptyFlow()
+ authenticationInteractor.authenticationMethod
+ .flatMapLatest { authMethod ->
+ if (authMethod == AuthenticationMethodModel.Sim) {
+ simBouncerInteractor.bouncerMessageChanged.map { simMsg ->
+ simMsg?.let { MessageViewModel(it) }
}
+ } else {
+ emptyFlow()
}
- .collectLatest {
- if (it != null) {
- message.value = it
- } else {
- resetToDefault.emit(Unit)
- }
+ }
+ .collectLatest {
+ if (it != null) {
+ message.value = it
+ } else {
+ resetToDefault.emit(Unit)
}
- }
+ }
}
- private fun listenForFaceMessages() {
+ private suspend fun listenForFaceMessages() {
// Listen for any events from face authentication and update the message shown on the
// bouncer.
- applicationScope.launch {
- biometricMessageInteractor.faceMessage
- .sample(
- authenticationInteractor.authenticationMethod,
- deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer,
- )
- .collectLatest { (faceMessage, authMethod, fingerprintAllowedOnBouncer) ->
- val isFaceAuthStrong = faceAuthInteractor.isFaceAuthStrong()
- val defaultPrimaryMessage =
- BouncerMessageStrings.defaultMessage(
- authMethod,
- fingerprintAllowedOnBouncer
+ biometricMessageInteractor.faceMessage
+ .sample(
+ authenticationInteractor.authenticationMethod,
+ deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer,
+ )
+ .collectLatest { (faceMessage, authMethod, fingerprintAllowedOnBouncer) ->
+ val isFaceAuthStrong = faceAuthInteractor.isFaceAuthStrong()
+ val defaultPrimaryMessage =
+ BouncerMessageStrings.defaultMessage(authMethod, fingerprintAllowedOnBouncer)
+ .primaryMessage
+ .toResString()
+ message.value =
+ when (faceMessage) {
+ is FaceTimeoutMessage ->
+ MessageViewModel(
+ text = defaultPrimaryMessage,
+ secondaryText = faceMessage.message,
+ isUpdateAnimated = true
)
- .primaryMessage
- .toResString()
- message.value =
- when (faceMessage) {
- is FaceTimeoutMessage ->
- MessageViewModel(
- text = defaultPrimaryMessage,
- secondaryText = faceMessage.message,
- isUpdateAnimated = true
- )
- is FaceLockoutMessage ->
- if (isFaceAuthStrong)
- BouncerMessageStrings.class3AuthLockedOut(authMethod)
- .toMessage()
- else
- BouncerMessageStrings.faceLockedOut(
- authMethod,
- fingerprintAllowedOnBouncer
- )
- .toMessage()
- is FaceFailureMessage ->
- BouncerMessageStrings.incorrectFaceInput(
+ is FaceLockoutMessage ->
+ if (isFaceAuthStrong)
+ BouncerMessageStrings.class3AuthLockedOut(authMethod).toMessage()
+ else
+ BouncerMessageStrings.faceLockedOut(
authMethod,
fingerprintAllowedOnBouncer
)
.toMessage()
- else ->
- MessageViewModel(
- text = defaultPrimaryMessage,
- secondaryText = faceMessage.message,
- isUpdateAnimated = false
+ is FaceFailureMessage ->
+ BouncerMessageStrings.incorrectFaceInput(
+ authMethod,
+ fingerprintAllowedOnBouncer
)
- }
- delay(MESSAGE_DURATION)
- resetToDefault.emit(Unit)
- }
- }
- }
-
- private fun listenForFingerprintMessages() {
- applicationScope.launch {
- // Listen for any events from fingerprint authentication and update the message shown
- // on the bouncer.
- biometricMessageInteractor.fingerprintMessage
- .sample(
- authenticationInteractor.authenticationMethod,
- deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer
- )
- .collectLatest { (fingerprintMessage, authMethod, isFingerprintAllowed) ->
- val defaultPrimaryMessage =
- BouncerMessageStrings.defaultMessage(authMethod, isFingerprintAllowed)
- .primaryMessage
- .toResString()
- message.value =
- when (fingerprintMessage) {
- is FingerprintLockoutMessage ->
- BouncerMessageStrings.class3AuthLockedOut(authMethod).toMessage()
- is FingerprintFailureMessage ->
- BouncerMessageStrings.incorrectFingerprintInput(authMethod)
- .toMessage()
- else ->
- MessageViewModel(
- text = defaultPrimaryMessage,
- secondaryText = fingerprintMessage.message,
- isUpdateAnimated = false
- )
- }
- delay(MESSAGE_DURATION)
- resetToDefault.emit(Unit)
- }
- }
- }
-
- private fun listenForBouncerEvents() {
- // Keeps the lockout message up-to-date.
- applicationScope.launch {
- bouncerInteractor.onLockoutStarted.collect { startLockoutCountdown() }
- }
-
- // Listens to relevant bouncer events
- applicationScope.launch {
- bouncerInteractor.onIncorrectBouncerInput
- .sample(
- authenticationInteractor.authenticationMethod,
- deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer
- )
- .collectLatest { (_, authMethod, isFingerprintAllowed) ->
- message.emit(
- BouncerMessageStrings.incorrectSecurityInput(
- authMethod,
- isFingerprintAllowed
+ .toMessage()
+ else ->
+ MessageViewModel(
+ text = defaultPrimaryMessage,
+ secondaryText = faceMessage.message,
+ isUpdateAnimated = false
)
- .toMessage()
+ }
+ delay(MESSAGE_DURATION)
+ resetToDefault.emit(Unit)
+ }
+ }
+
+ private suspend fun listenForFingerprintMessages() {
+ // Listen for any events from fingerprint authentication and update the message shown
+ // on the bouncer.
+ biometricMessageInteractor.fingerprintMessage
+ .sample(
+ authenticationInteractor.authenticationMethod,
+ deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer
+ )
+ .collectLatest { (fingerprintMessage, authMethod, isFingerprintAllowed) ->
+ val defaultPrimaryMessage =
+ BouncerMessageStrings.defaultMessage(authMethod, isFingerprintAllowed)
+ .primaryMessage
+ .toResString()
+ message.value =
+ when (fingerprintMessage) {
+ is FingerprintLockoutMessage ->
+ BouncerMessageStrings.class3AuthLockedOut(authMethod).toMessage()
+ is FingerprintFailureMessage ->
+ BouncerMessageStrings.incorrectFingerprintInput(authMethod).toMessage()
+ else ->
+ MessageViewModel(
+ text = defaultPrimaryMessage,
+ secondaryText = fingerprintMessage.message,
+ isUpdateAnimated = false
+ )
+ }
+ delay(MESSAGE_DURATION)
+ resetToDefault.emit(Unit)
+ }
+ }
+
+ private suspend fun listenForBouncerEvents() {
+ coroutineScope {
+ // Keeps the lockout message up-to-date.
+ launch { bouncerInteractor.onLockoutStarted.collect { startLockoutCountdown() } }
+
+ // Listens to relevant bouncer events
+ launch {
+ bouncerInteractor.onIncorrectBouncerInput
+ .sample(
+ authenticationInteractor.authenticationMethod,
+ deviceEntryBiometricsAllowedInteractor
+ .isFingerprintCurrentlyAllowedOnBouncer
)
- delay(MESSAGE_DURATION)
- resetToDefault.emit(Unit)
- }
+ .collectLatest { (_, authMethod, isFingerprintAllowed) ->
+ message.emit(
+ BouncerMessageStrings.incorrectSecurityInput(
+ authMethod,
+ isFingerprintAllowed
+ )
+ .toMessage()
+ )
+ delay(MESSAGE_DURATION)
+ resetToDefault.emit(Unit)
+ }
+ }
}
}
@@ -323,10 +330,10 @@
}
/** Shows the countdown message and refreshes it every second. */
- private fun startLockoutCountdown() {
+ private suspend fun startLockoutCountdown() {
lockoutCountdownJob?.cancel()
- lockoutCountdownJob =
- applicationScope.launch {
+ lockoutCountdownJob = coroutineScope {
+ launch {
authenticationInteractor.authenticationMethod.collectLatest { authMethod ->
do {
val remainingSeconds = remainingLockoutSeconds()
@@ -352,6 +359,7 @@
lockoutCountdownJob = null
}
}
+ }
}
private fun remainingLockoutSeconds(): Int {
@@ -365,20 +373,9 @@
private fun Int.toResString(): String = applicationContext.getString(this)
- init {
- if (flags.isComposeBouncerOrSceneContainerEnabled()) {
- applicationScope.launch {
- // Update the lockout countdown whenever the selected user is switched.
- selectedUser.collect { startLockoutCountdown() }
- }
-
- defaultBouncerMessageInitializer()
-
- listenForSimBouncerEvents()
- listenForBouncerEvents()
- listenForFaceMessages()
- listenForFingerprintMessages()
- }
+ @AssistedFactory
+ interface Factory {
+ fun create(): BouncerMessageViewModel
}
companion object {
@@ -398,40 +395,3 @@
*/
val isUpdateAnimated: Boolean = true,
)
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@Module
-object BouncerMessageViewModelModule {
-
- @Provides
- @SysUISingleton
- fun viewModel(
- @Application applicationContext: Context,
- @Application applicationScope: CoroutineScope,
- bouncerInteractor: BouncerInteractor,
- simBouncerInteractor: SimBouncerInteractor,
- authenticationInteractor: AuthenticationInteractor,
- clock: SystemClock,
- biometricMessageInteractor: BiometricMessageInteractor,
- faceAuthInteractor: DeviceEntryFaceAuthInteractor,
- deviceUnlockedInteractor: DeviceUnlockedInteractor,
- deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
- flags: ComposeBouncerFlags,
- userSwitcherViewModel: UserSwitcherViewModel,
- ): BouncerMessageViewModel {
- return BouncerMessageViewModel(
- applicationContext = applicationContext,
- applicationScope = applicationScope,
- bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = simBouncerInteractor,
- authenticationInteractor = authenticationInteractor,
- clock = clock,
- biometricMessageInteractor = biometricMessageInteractor,
- faceAuthInteractor = faceAuthInteractor,
- deviceUnlockedInteractor = deviceUnlockedInteractor,
- deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor,
- flags = flags,
- selectedUser = userSwitcherViewModel.selectedUser,
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
new file mode 100644
index 0000000..2a27271
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.map
+
+/**
+ * Models UI state for user actions that can lead to navigation to other scenes when showing the
+ * bouncer scene.
+ */
+class BouncerSceneActionsViewModel
+@AssistedInject
+constructor(
+ private val bouncerInteractor: BouncerInteractor,
+) : SceneActionsViewModel() {
+
+ override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+ bouncerInteractor.dismissDestination
+ .map { prevScene ->
+ mapOf(
+ Back to UserActionResult(prevScene),
+ Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
+ )
+ }
+ .collectLatest { actions -> setActions(actions) }
+ }
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): BouncerSceneActionsViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
new file mode 100644
index 0000000..aede63b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -0,0 +1,362 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources
+import android.content.Context
+import android.graphics.Bitmap
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.type
+import androidx.core.graphics.drawable.toBitmap
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
+import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
+import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/** Models UI state for the content of the bouncer scene. */
+class BouncerSceneContentViewModel
+@AssistedInject
+constructor(
+ @Application private val applicationContext: Context,
+ private val bouncerInteractor: BouncerInteractor,
+ private val authenticationInteractor: AuthenticationInteractor,
+ private val devicePolicyManager: DevicePolicyManager,
+ private val bouncerMessageViewModelFactory: BouncerMessageViewModel.Factory,
+ private val flags: ComposeBouncerFlags,
+ private val userSwitcher: UserSwitcherViewModel,
+ private val actionButtonInteractor: BouncerActionButtonInteractor,
+ private val pinViewModelFactory: PinBouncerViewModel.Factory,
+ private val patternViewModelFactory: PatternBouncerViewModel.Factory,
+ private val passwordViewModelFactory: PasswordBouncerViewModel.Factory,
+) : SysUiViewModel() {
+ private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
+ val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
+
+ val message: BouncerMessageViewModel by lazy { bouncerMessageViewModelFactory.create() }
+
+ private val _userSwitcherDropdown =
+ MutableStateFlow<List<UserSwitcherDropdownItemViewModel>>(emptyList())
+ val userSwitcherDropdown: StateFlow<List<UserSwitcherDropdownItemViewModel>> =
+ _userSwitcherDropdown.asStateFlow()
+
+ val isUserSwitcherVisible: Boolean
+ get() = bouncerInteractor.isUserSwitcherVisible
+
+ /** View-model for the current UI, based on the current authentication method. */
+ private val _authMethodViewModel = MutableStateFlow<AuthMethodBouncerViewModel?>(null)
+ val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> =
+ _authMethodViewModel.asStateFlow()
+
+ /**
+ * A message for a dialog to show when the user has attempted the wrong credential too many
+ * times and now must wait a while before attempting again.
+ *
+ * If `null`, the lockout dialog should not be shown.
+ */
+ private val lockoutDialogMessage = MutableStateFlow<String?>(null)
+
+ /**
+ * A message for a dialog to show when the user has attempted the wrong credential too many
+ * times and their user/profile/device data is at risk of being wiped due to a Device Manager
+ * policy.
+ *
+ * If `null`, the wipe dialog should not be shown.
+ */
+ private val wipeDialogMessage = MutableStateFlow<String?>(null)
+
+ private val _dialogViewModel = MutableStateFlow<DialogViewModel?>(createDialogViewModel())
+ /**
+ * Models the dialog to be shown to the user, or `null` if no dialog should be shown.
+ *
+ * Once the dialog is shown, the UI should call [DialogViewModel.onDismiss] when the user
+ * dismisses this dialog.
+ */
+ val dialogViewModel: StateFlow<DialogViewModel?> = _dialogViewModel.asStateFlow()
+
+ private val _actionButton = MutableStateFlow<BouncerActionButtonModel?>(null)
+ /**
+ * The bouncer action button (Return to Call / Emergency Call). If `null`, the button should not
+ * be shown.
+ */
+ val actionButton: StateFlow<BouncerActionButtonModel?> = _actionButton.asStateFlow()
+
+ private val _isSideBySideSupported =
+ MutableStateFlow(isSideBySideSupported(authMethodViewModel.value))
+ /**
+ * Whether the "side-by-side" layout is supported.
+ *
+ * When presented on its own, without a user switcher (e.g. not on communal devices like
+ * tablets, for example), some authentication method UIs don't do well if they're shown in the
+ * side-by-side layout; these need to be shown with the standard layout so they can take up as
+ * much width as possible.
+ */
+ val isSideBySideSupported: StateFlow<Boolean> = _isSideBySideSupported.asStateFlow()
+
+ private val _isFoldSplitRequired =
+ MutableStateFlow(isFoldSplitRequired(authMethodViewModel.value))
+ /**
+ * Whether the splitting the UI around the fold seam (where the hinge is on a foldable device)
+ * is required.
+ */
+ val isFoldSplitRequired: StateFlow<Boolean> = _isFoldSplitRequired.asStateFlow()
+
+ private val _isInputEnabled =
+ MutableStateFlow(authenticationInteractor.lockoutEndTimestamp == null)
+ private val isInputEnabled: StateFlow<Boolean> = _isInputEnabled.asStateFlow()
+
+ override suspend fun onActivated() {
+ coroutineScope {
+ launch { message.activate() }
+ launch {
+ authenticationInteractor.authenticationMethod
+ .map(::getChildViewModel)
+ .collectLatest { childViewModelOrNull ->
+ _authMethodViewModel.value = childViewModelOrNull
+ childViewModelOrNull?.activate()
+ }
+ }
+
+ launch {
+ authenticationInteractor.upcomingWipe.collect { wipeModel ->
+ wipeDialogMessage.value = wipeModel?.message
+ }
+ }
+
+ launch {
+ userSwitcher.selectedUser
+ .map { it.image.toBitmap() }
+ .collectLatest { _selectedUserImage.value = it }
+ }
+
+ launch {
+ combine(
+ userSwitcher.users,
+ userSwitcher.menu,
+ ) { users, actions ->
+ users.map { user ->
+ UserSwitcherDropdownItemViewModel(
+ icon = Icon.Loaded(user.image, contentDescription = null),
+ text = user.name,
+ onClick = user.onClicked ?: {},
+ )
+ } +
+ actions.map { action ->
+ UserSwitcherDropdownItemViewModel(
+ icon =
+ Icon.Resource(
+ action.iconResourceId,
+ contentDescription = null
+ ),
+ text = Text.Resource(action.textResourceId),
+ onClick = action.onClicked,
+ )
+ }
+ }
+ .collectLatest { _userSwitcherDropdown.value = it }
+ }
+
+ launch {
+ combine(wipeDialogMessage, lockoutDialogMessage) { _, _ -> createDialogViewModel() }
+ .collectLatest { _dialogViewModel.value = it }
+ }
+
+ launch {
+ actionButtonInteractor.actionButton.collectLatest { _actionButton.value = it }
+ }
+
+ launch {
+ authMethodViewModel
+ .map { authMethod -> isSideBySideSupported(authMethod) }
+ .collectLatest { _isSideBySideSupported.value = it }
+ }
+
+ launch {
+ authMethodViewModel
+ .map { authMethod -> isFoldSplitRequired(authMethod) }
+ .collectLatest { _isFoldSplitRequired.value = it }
+ }
+
+ launch {
+ message.isLockoutMessagePresent
+ .map { lockoutMessagePresent -> !lockoutMessagePresent }
+ .collectLatest { _isInputEnabled.value = it }
+ }
+ }
+ }
+
+ private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
+ return isUserSwitcherVisible || authMethod !is PasswordBouncerViewModel
+ }
+
+ private fun isFoldSplitRequired(authMethod: AuthMethodBouncerViewModel?): Boolean {
+ return authMethod !is PasswordBouncerViewModel
+ }
+
+ private fun getChildViewModel(
+ authenticationMethod: AuthenticationMethodModel,
+ ): AuthMethodBouncerViewModel? {
+ // If the current child view-model matches the authentication method, reuse it instead of
+ // creating a new instance.
+ val childViewModel = authMethodViewModel.value
+ if (authenticationMethod == childViewModel?.authenticationMethod) {
+ return childViewModel
+ }
+
+ return when (authenticationMethod) {
+ is AuthenticationMethodModel.Pin ->
+ pinViewModelFactory.create(
+ authenticationMethod = authenticationMethod,
+ onIntentionalUserInput = ::onIntentionalUserInput,
+ isInputEnabled = isInputEnabled,
+ )
+ is AuthenticationMethodModel.Sim ->
+ pinViewModelFactory.create(
+ authenticationMethod = authenticationMethod,
+ onIntentionalUserInput = ::onIntentionalUserInput,
+ isInputEnabled = isInputEnabled,
+ )
+ is AuthenticationMethodModel.Password ->
+ passwordViewModelFactory.create(
+ onIntentionalUserInput = ::onIntentionalUserInput,
+ isInputEnabled = isInputEnabled,
+ )
+ is AuthenticationMethodModel.Pattern ->
+ patternViewModelFactory.create(
+ onIntentionalUserInput = ::onIntentionalUserInput,
+ isInputEnabled = isInputEnabled,
+ )
+ else -> null
+ }
+ }
+
+ private fun onIntentionalUserInput() {
+ message.showDefaultMessage()
+ bouncerInteractor.onIntentionalUserInput()
+ }
+
+ /**
+ * @return A message warning the user that the user/profile/device will be wiped upon a further
+ * [AuthenticationWipeModel.remainingAttempts] unsuccessful authentication attempts.
+ */
+ private fun AuthenticationWipeModel.getAlmostAtWipeMessage(): String {
+ val message =
+ applicationContext.getString(
+ wipeTarget.messageIdForAlmostWipe,
+ failedAttempts,
+ remainingAttempts,
+ )
+ return if (wipeTarget == AuthenticationWipeModel.WipeTarget.ManagedProfile) {
+ devicePolicyManager.resources.getString(
+ DevicePolicyResources.Strings.SystemUi
+ .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ALMOST_ERASING_PROFILE,
+ { message },
+ failedAttempts,
+ remainingAttempts,
+ ) ?: message
+ } else {
+ message
+ }
+ }
+
+ /**
+ * @return A message informing the user that their user/profile/device will be wiped promptly.
+ */
+ private fun AuthenticationWipeModel.getWipeMessage(): String {
+ val message = applicationContext.getString(wipeTarget.messageIdForWipe, failedAttempts)
+ return if (wipeTarget == AuthenticationWipeModel.WipeTarget.ManagedProfile) {
+ devicePolicyManager.resources.getString(
+ DevicePolicyResources.Strings.SystemUi
+ .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE,
+ { message },
+ failedAttempts,
+ ) ?: message
+ } else {
+ message
+ }
+ }
+
+ private val AuthenticationWipeModel.message: String
+ get() = if (remainingAttempts > 0) getAlmostAtWipeMessage() else getWipeMessage()
+
+ private fun createDialogViewModel(): DialogViewModel? {
+ val wipeText = wipeDialogMessage.value
+ val lockoutText = lockoutDialogMessage.value
+ return when {
+ // The wipe dialog takes priority over the lockout dialog.
+ wipeText != null ->
+ DialogViewModel(
+ text = wipeText,
+ onDismiss = { wipeDialogMessage.value = null },
+ )
+ lockoutText != null ->
+ DialogViewModel(
+ text = lockoutText,
+ onDismiss = { lockoutDialogMessage.value = null },
+ )
+ else -> null // No dialog to show.
+ }
+ }
+
+ /**
+ * Notifies that a key event has occurred.
+ *
+ * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise.
+ */
+ fun onKeyEvent(keyEvent: KeyEvent): Boolean {
+ return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
+ keyEvent.type,
+ keyEvent.nativeKeyEvent.keyCode
+ ) ?: false
+ }
+
+ data class DialogViewModel(
+ val text: String,
+
+ /** Callback to run after the dialog has been dismissed by the user. */
+ val onDismiss: () -> Unit,
+ )
+
+ data class UserSwitcherDropdownItemViewModel(
+ val icon: Icon,
+ val text: Text,
+ val onClick: () -> Unit,
+ )
+
+ @AssistedFactory
+ interface Factory {
+ fun create(): BouncerSceneContentViewModel
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
deleted file mode 100644
index e2089bb..0000000
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bouncer.ui.viewmodel
-
-import android.app.admin.DevicePolicyManager
-import android.app.admin.DevicePolicyResources
-import android.content.Context
-import android.graphics.Bitmap
-import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.input.key.type
-import androidx.core.graphics.drawable.toBitmap
-import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
-import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
-import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
-import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.user.ui.viewmodel.UserActionViewModel
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
-import dagger.Module
-import dagger.Provides
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.job
-import kotlinx.coroutines.launch
-
-/** Holds UI state and handles user input on bouncer UIs. */
-class BouncerViewModel(
- @Application private val applicationContext: Context,
- @Deprecated("TODO(b/354270224): remove this. Injecting CoroutineScope to view-models is banned")
- @Application
- private val applicationScope: CoroutineScope,
- @Main private val mainDispatcher: CoroutineDispatcher,
- private val bouncerInteractor: BouncerInteractor,
- private val inputMethodInteractor: InputMethodInteractor,
- private val simBouncerInteractor: SimBouncerInteractor,
- private val authenticationInteractor: AuthenticationInteractor,
- private val selectedUserInteractor: SelectedUserInteractor,
- private val devicePolicyManager: DevicePolicyManager,
- bouncerMessageViewModel: BouncerMessageViewModel,
- flags: ComposeBouncerFlags,
- selectedUser: Flow<UserViewModel>,
- users: Flow<List<UserViewModel>>,
- userSwitcherMenu: Flow<List<UserActionViewModel>>,
- actionButton: Flow<BouncerActionButtonModel?>,
-) {
- val selectedUserImage: StateFlow<Bitmap?> =
- selectedUser
- .map { it.image.toBitmap() }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null,
- )
-
- val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
- bouncerInteractor.dismissDestination.map { prevScene ->
- mapOf(
- Back to UserActionResult(prevScene),
- Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
- )
- }
-
- val message: BouncerMessageViewModel = bouncerMessageViewModel
-
- val userSwitcherDropdown: StateFlow<List<UserSwitcherDropdownItemViewModel>> =
- combine(
- users,
- userSwitcherMenu,
- ) { users, actions ->
- users.map { user ->
- UserSwitcherDropdownItemViewModel(
- icon = Icon.Loaded(user.image, contentDescription = null),
- text = user.name,
- onClick = user.onClicked ?: {},
- )
- } +
- actions.map { action ->
- UserSwitcherDropdownItemViewModel(
- icon = Icon.Resource(action.iconResourceId, contentDescription = null),
- text = Text.Resource(action.textResourceId),
- onClick = action.onClicked,
- )
- }
- }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = emptyList(),
- )
-
- val isUserSwitcherVisible: Boolean
- get() = bouncerInteractor.isUserSwitcherVisible
-
- // Handle to the scope of the child ViewModel (stored in [authMethod]).
- private var childViewModelScope: CoroutineScope? = null
-
- /** View-model for the current UI, based on the current authentication method. */
- val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> =
- authenticationInteractor.authenticationMethod
- .map(::getChildViewModel)
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null,
- )
-
- /**
- * A message for a dialog to show when the user has attempted the wrong credential too many
- * times and now must wait a while before attempting again.
- *
- * If `null`, the lockout dialog should not be shown.
- */
- private val lockoutDialogMessage = MutableStateFlow<String?>(null)
-
- /**
- * A message for a dialog to show when the user has attempted the wrong credential too many
- * times and their user/profile/device data is at risk of being wiped due to a Device Manager
- * policy.
- *
- * If `null`, the wipe dialog should not be shown.
- */
- private val wipeDialogMessage = MutableStateFlow<String?>(null)
-
- /**
- * Models the dialog to be shown to the user, or `null` if no dialog should be shown.
- *
- * Once the dialog is shown, the UI should call [DialogViewModel.onDismiss] when the user
- * dismisses this dialog.
- */
- val dialogViewModel: StateFlow<DialogViewModel?> =
- combine(wipeDialogMessage, lockoutDialogMessage) { _, _ -> createDialogViewModel() }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = createDialogViewModel(),
- )
-
- /**
- * The bouncer action button (Return to Call / Emergency Call). If `null`, the button should not
- * be shown.
- */
- val actionButton: StateFlow<BouncerActionButtonModel?> =
- actionButton.stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = null
- )
-
- /**
- * Whether the "side-by-side" layout is supported.
- *
- * When presented on its own, without a user switcher (e.g. not on communal devices like
- * tablets, for example), some authentication method UIs don't do well if they're shown in the
- * side-by-side layout; these need to be shown with the standard layout so they can take up as
- * much width as possible.
- */
- val isSideBySideSupported: StateFlow<Boolean> =
- authMethodViewModel
- .map { authMethod -> isSideBySideSupported(authMethod) }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = isSideBySideSupported(authMethodViewModel.value),
- )
-
- /**
- * Whether the splitting the UI around the fold seam (where the hinge is on a foldable device)
- * is required.
- */
- val isFoldSplitRequired: StateFlow<Boolean> =
- authMethodViewModel
- .map { authMethod -> isFoldSplitRequired(authMethod) }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = isFoldSplitRequired(authMethodViewModel.value),
- )
-
- private val isInputEnabled: StateFlow<Boolean> =
- bouncerMessageViewModel.isLockoutMessagePresent
- .map { lockoutMessagePresent -> !lockoutMessagePresent }
- .stateIn(
- scope = applicationScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = authenticationInteractor.lockoutEndTimestamp == null,
- )
-
- init {
- if (flags.isComposeBouncerOrSceneContainerEnabled()) {
- // Keeps the upcoming wipe dialog up-to-date.
- applicationScope.launch {
- authenticationInteractor.upcomingWipe.collect { wipeModel ->
- wipeDialogMessage.value = wipeModel?.message
- }
- }
- }
- }
-
- private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
- return isUserSwitcherVisible || authMethod !is PasswordBouncerViewModel
- }
-
- private fun isFoldSplitRequired(authMethod: AuthMethodBouncerViewModel?): Boolean {
- return authMethod !is PasswordBouncerViewModel
- }
-
- private fun getChildViewModel(
- authenticationMethod: AuthenticationMethodModel,
- ): AuthMethodBouncerViewModel? {
- // If the current child view-model matches the authentication method, reuse it instead of
- // creating a new instance.
- val childViewModel = authMethodViewModel.value
- if (authenticationMethod == childViewModel?.authenticationMethod) {
- return childViewModel
- }
-
- childViewModelScope?.cancel()
- val newViewModelScope = createChildCoroutineScope(applicationScope)
- childViewModelScope = newViewModelScope
- return when (authenticationMethod) {
- is AuthenticationMethodModel.Pin ->
- PinBouncerViewModel(
- applicationContext = applicationContext,
- viewModelScope = newViewModelScope,
- interactor = bouncerInteractor,
- isInputEnabled = isInputEnabled,
- simBouncerInteractor = simBouncerInteractor,
- authenticationMethod = authenticationMethod,
- onIntentionalUserInput = ::onIntentionalUserInput
- )
- is AuthenticationMethodModel.Sim ->
- PinBouncerViewModel(
- applicationContext = applicationContext,
- viewModelScope = newViewModelScope,
- interactor = bouncerInteractor,
- isInputEnabled = isInputEnabled,
- simBouncerInteractor = simBouncerInteractor,
- authenticationMethod = authenticationMethod,
- onIntentionalUserInput = ::onIntentionalUserInput
- )
- is AuthenticationMethodModel.Password ->
- PasswordBouncerViewModel(
- viewModelScope = newViewModelScope,
- isInputEnabled = isInputEnabled,
- interactor = bouncerInteractor,
- inputMethodInteractor = inputMethodInteractor,
- selectedUserInteractor = selectedUserInteractor,
- onIntentionalUserInput = ::onIntentionalUserInput
- )
- is AuthenticationMethodModel.Pattern ->
- PatternBouncerViewModel(
- applicationContext = applicationContext,
- viewModelScope = newViewModelScope,
- interactor = bouncerInteractor,
- isInputEnabled = isInputEnabled,
- onIntentionalUserInput = ::onIntentionalUserInput
- )
- else -> null
- }
- }
-
- private fun onIntentionalUserInput() {
- message.showDefaultMessage()
- bouncerInteractor.onIntentionalUserInput()
- }
-
- private fun createChildCoroutineScope(parentScope: CoroutineScope): CoroutineScope {
- return CoroutineScope(
- SupervisorJob(parent = parentScope.coroutineContext.job) + mainDispatcher
- )
- }
-
- /**
- * @return A message warning the user that the user/profile/device will be wiped upon a further
- * [AuthenticationWipeModel.remainingAttempts] unsuccessful authentication attempts.
- */
- private fun AuthenticationWipeModel.getAlmostAtWipeMessage(): String {
- val message =
- applicationContext.getString(
- wipeTarget.messageIdForAlmostWipe,
- failedAttempts,
- remainingAttempts,
- )
- return if (wipeTarget == AuthenticationWipeModel.WipeTarget.ManagedProfile) {
- devicePolicyManager.resources.getString(
- DevicePolicyResources.Strings.SystemUi
- .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ALMOST_ERASING_PROFILE,
- { message },
- failedAttempts,
- remainingAttempts,
- ) ?: message
- } else {
- message
- }
- }
-
- /**
- * @return A message informing the user that their user/profile/device will be wiped promptly.
- */
- private fun AuthenticationWipeModel.getWipeMessage(): String {
- val message = applicationContext.getString(wipeTarget.messageIdForWipe, failedAttempts)
- return if (wipeTarget == AuthenticationWipeModel.WipeTarget.ManagedProfile) {
- devicePolicyManager.resources.getString(
- DevicePolicyResources.Strings.SystemUi
- .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE,
- { message },
- failedAttempts,
- ) ?: message
- } else {
- message
- }
- }
-
- private val AuthenticationWipeModel.message: String
- get() = if (remainingAttempts > 0) getAlmostAtWipeMessage() else getWipeMessage()
-
- private fun createDialogViewModel(): DialogViewModel? {
- val wipeText = wipeDialogMessage.value
- val lockoutText = lockoutDialogMessage.value
- return when {
- // The wipe dialog takes priority over the lockout dialog.
- wipeText != null ->
- DialogViewModel(
- text = wipeText,
- onDismiss = { wipeDialogMessage.value = null },
- )
- lockoutText != null ->
- DialogViewModel(
- text = lockoutText,
- onDismiss = { lockoutDialogMessage.value = null },
- )
- else -> null // No dialog to show.
- }
- }
-
- /**
- * Notifies that a key event has occurred.
- *
- * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise.
- */
- fun onKeyEvent(keyEvent: KeyEvent): Boolean {
- return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
- keyEvent.type,
- keyEvent.nativeKeyEvent.keyCode
- ) ?: false
- }
-
- data class DialogViewModel(
- val text: String,
-
- /** Callback to run after the dialog has been dismissed by the user. */
- val onDismiss: () -> Unit,
- )
-
- data class UserSwitcherDropdownItemViewModel(
- val icon: Icon,
- val text: Text,
- val onClick: () -> Unit,
- )
-}
-
-@Module
-object BouncerViewModelModule {
-
- @Provides
- @SysUISingleton
- fun viewModel(
- @Application applicationContext: Context,
- @Application applicationScope: CoroutineScope,
- @Main mainDispatcher: CoroutineDispatcher,
- bouncerInteractor: BouncerInteractor,
- imeInteractor: InputMethodInteractor,
- simBouncerInteractor: SimBouncerInteractor,
- actionButtonInteractor: BouncerActionButtonInteractor,
- authenticationInteractor: AuthenticationInteractor,
- selectedUserInteractor: SelectedUserInteractor,
- flags: ComposeBouncerFlags,
- userSwitcherViewModel: UserSwitcherViewModel,
- devicePolicyManager: DevicePolicyManager,
- bouncerMessageViewModel: BouncerMessageViewModel,
- ): BouncerViewModel {
- return BouncerViewModel(
- applicationContext = applicationContext,
- applicationScope = applicationScope,
- mainDispatcher = mainDispatcher,
- bouncerInteractor = bouncerInteractor,
- inputMethodInteractor = imeInteractor,
- simBouncerInteractor = simBouncerInteractor,
- authenticationInteractor = authenticationInteractor,
- selectedUserInteractor = selectedUserInteractor,
- devicePolicyManager = devicePolicyManager,
- bouncerMessageViewModel = bouncerMessageViewModel,
- flags = flags,
- selectedUser = userSwitcherViewModel.selectedUser,
- users = userSwitcherViewModel.users,
- userSwitcherMenu = userSwitcherViewModel.menu,
- actionButton = actionButtonInteractor.actionButton,
- )
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 052fb6b..9ead7a0 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -23,29 +23,33 @@
import com.android.systemui.res.R
import com.android.systemui.user.domain.interactor.SelectedUserInteractor
import com.android.systemui.util.kotlin.onSubscriberAdded
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
/** Holds UI state and handles user input for the password bouncer UI. */
-class PasswordBouncerViewModel(
- viewModelScope: CoroutineScope,
- isInputEnabled: StateFlow<Boolean>,
+class PasswordBouncerViewModel
+@AssistedInject
+constructor(
interactor: BouncerInteractor,
- private val onIntentionalUserInput: () -> Unit,
private val inputMethodInteractor: InputMethodInteractor,
private val selectedUserInteractor: SelectedUserInteractor,
+ @Assisted isInputEnabled: StateFlow<Boolean>,
+ @Assisted private val onIntentionalUserInput: () -> Unit,
) :
AuthMethodBouncerViewModel(
- viewModelScope = viewModelScope,
interactor = interactor,
isInputEnabled = isInputEnabled,
) {
@@ -59,28 +63,70 @@
override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
+ private val _isImeSwitcherButtonVisible = MutableStateFlow(false)
/** Informs the UI whether the input method switcher button should be visible. */
- val isImeSwitcherButtonVisible: StateFlow<Boolean> = imeSwitcherRefreshingFlow()
+ val isImeSwitcherButtonVisible: StateFlow<Boolean> = _isImeSwitcherButtonVisible.asStateFlow()
/** Whether the text field element currently has focus. */
private val isTextFieldFocused = MutableStateFlow(false)
+ private val _isTextFieldFocusRequested =
+ MutableStateFlow(isInputEnabled.value && !isTextFieldFocused.value)
/** Whether the UI should request focus on the text field element. */
- val isTextFieldFocusRequested =
- combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> hasInput && !hasFocus }
- .stateIn(
- scope = viewModelScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = isInputEnabled.value && !isTextFieldFocused.value,
- )
+ val isTextFieldFocusRequested = _isTextFieldFocusRequested.asStateFlow()
+ private val _selectedUserId = MutableStateFlow(selectedUserInteractor.getSelectedUserId())
/** The ID of the currently-selected user. */
- val selectedUserId: StateFlow<Int> =
- selectedUserInteractor.selectedUser.stateIn(
- scope = viewModelScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = selectedUserInteractor.getSelectedUserId(),
- )
+ val selectedUserId: StateFlow<Int> = _selectedUserId.asStateFlow()
+
+ private val requests = Channel<Request>(Channel.BUFFERED)
+
+ override suspend fun onActivated() {
+ coroutineScope {
+ launch { super.onActivated() }
+ launch {
+ requests.receiveAsFlow().collect { request ->
+ when (request) {
+ is OnImeSwitcherButtonClicked -> {
+ inputMethodInteractor.showInputMethodPicker(
+ displayId = request.displayId,
+ showAuxiliarySubtypes = false,
+ )
+ }
+ is OnImeDismissed -> {
+ interactor.onImeHiddenByUser()
+ }
+ }
+ }
+ }
+ launch {
+ combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus ->
+ hasInput && !hasFocus
+ }
+ .collectLatest { _isTextFieldFocusRequested.value = it }
+ }
+ launch {
+ selectedUserInteractor.selectedUser.collectLatest { _selectedUserId.value = it }
+ }
+ launch {
+ // Re-fetch the currently-enabled IMEs whenever the selected user changes, and
+ // whenever
+ // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
+ combine(
+ // InputMethodManagerService sometimes takes some time to update its
+ // internal
+ // state when the selected user changes. As a workaround, delay fetching the
+ // IME
+ // info.
+ selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) },
+ _isImeSwitcherButtonVisible.onSubscriberAdded()
+ ) { selectedUserId, _ ->
+ inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
+ }
+ .collectLatest { _isImeSwitcherButtonVisible.value = it }
+ }
+ }
+ }
override fun onHidden() {
super.onHidden()
@@ -106,9 +152,7 @@
/** Notifies that the user clicked the button to change the input method. */
fun onImeSwitcherButtonClicked(displayId: Int) {
- viewModelScope.launch {
- inputMethodInteractor.showInputMethodPicker(displayId, showAuxiliarySubtypes = false)
- }
+ requests.trySend(OnImeSwitcherButtonClicked(displayId))
}
/** Notifies that the user has pressed the key for attempting to authenticate the password. */
@@ -120,7 +164,7 @@
/** Notifies that the user has dismissed the software keyboard (IME). */
fun onImeDismissed() {
- viewModelScope.launch { interactor.onImeHiddenByUser() }
+ requests.trySend(OnImeDismissed)
}
/** Notifies that the password text field has gained or lost focus. */
@@ -128,34 +172,21 @@
isTextFieldFocused.value = isFocused
}
- /**
- * Whether the input method switcher button should be displayed in the password bouncer UI. The
- * value may be stale at the moment of subscription to this flow, but it is guaranteed to be
- * shortly updated with a fresh value.
- *
- * Note: Each added subscription triggers an IPC call in the background, so this should only be
- * subscribed to by the UI once in its lifecycle (i.e. when the bouncer is shown).
- */
- private fun imeSwitcherRefreshingFlow(): StateFlow<Boolean> {
- val isImeSwitcherButtonVisible = MutableStateFlow(value = false)
- viewModelScope.launch {
- // Re-fetch the currently-enabled IMEs whenever the selected user changes, and whenever
- // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
- combine(
- // InputMethodManagerService sometimes takes some time to update its internal
- // state when the selected user changes. As a workaround, delay fetching the IME
- // info.
- selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) },
- isImeSwitcherButtonVisible.onSubscriberAdded()
- ) { selectedUserId, _ ->
- inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
- }
- .collect { isImeSwitcherButtonVisible.value = it }
- }
- return isImeSwitcherButtonVisible.asStateFlow()
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ isInputEnabled: StateFlow<Boolean>,
+ onIntentionalUserInput: () -> Unit,
+ ): PasswordBouncerViewModel
}
companion object {
@VisibleForTesting val DELAY_TO_FETCH_IMES = 300.milliseconds
}
+
+ private sealed interface Request
+
+ private data class OnImeSwitcherButtonClicked(val displayId: Int) : Request
+
+ private data object OnImeDismissed : Request
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 8b9c0a9a..b1df04b 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -22,28 +22,32 @@
import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.res.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlin.math.max
import kotlin.math.min
import kotlin.math.pow
import kotlin.math.sqrt
-import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
/** Holds UI state and handles user input for the pattern bouncer UI. */
-class PatternBouncerViewModel(
+class PatternBouncerViewModel
+@AssistedInject
+constructor(
private val applicationContext: Context,
- viewModelScope: CoroutineScope,
interactor: BouncerInteractor,
- isInputEnabled: StateFlow<Boolean>,
- private val onIntentionalUserInput: () -> Unit,
+ @Assisted isInputEnabled: StateFlow<Boolean>,
+ @Assisted private val onIntentionalUserInput: () -> Unit,
) :
AuthMethodBouncerViewModel(
- viewModelScope = viewModelScope,
interactor = interactor,
isInputEnabled = isInputEnabled,
) {
@@ -54,17 +58,10 @@
/** The number of rows in the dot grid. */
val rowCount = 3
- private val _selectedDots = MutableStateFlow<LinkedHashSet<PatternDotViewModel>>(linkedSetOf())
-
+ private val selectedDotSet = MutableStateFlow<LinkedHashSet<PatternDotViewModel>>(linkedSetOf())
+ private val selectedDotList = MutableStateFlow(selectedDotSet.value.toList())
/** The dots that were selected by the user, in the order of selection. */
- val selectedDots: StateFlow<List<PatternDotViewModel>> =
- _selectedDots
- .map { it.toList() }
- .stateIn(
- scope = viewModelScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = emptyList(),
- )
+ val selectedDots: StateFlow<List<PatternDotViewModel>> = selectedDotList.asStateFlow()
private val _currentDot = MutableStateFlow<PatternDotViewModel?>(null)
@@ -83,6 +80,17 @@
override val lockoutMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message
+ override suspend fun onActivated() {
+ coroutineScope {
+ launch { super.onActivated() }
+ launch {
+ selectedDotSet
+ .map { it.toList() }
+ .collectLatest { selectedDotList.value = it.toList() }
+ }
+ }
+ }
+
/** Notifies that the user has started a drag gesture across the dot grid. */
fun onDragStart() {
onIntentionalUserInput()
@@ -120,7 +128,7 @@
}
val hitDot = dots.value.firstOrNull { dot -> dot.x == dotColumn && dot.y == dotRow }
- if (hitDot != null && !_selectedDots.value.contains(hitDot)) {
+ if (hitDot != null && !selectedDotSet.value.contains(hitDot)) {
val skippedOverDots =
currentDot.value?.let { previousDot ->
buildList {
@@ -147,9 +155,9 @@
}
} ?: emptyList()
- _selectedDots.value =
+ selectedDotSet.value =
linkedSetOf<PatternDotViewModel>().apply {
- addAll(_selectedDots.value)
+ addAll(selectedDotSet.value)
addAll(skippedOverDots)
add(hitDot)
}
@@ -172,11 +180,11 @@
override fun clearInput() {
_dots.value = defaultDots()
_currentDot.value = null
- _selectedDots.value = linkedSetOf()
+ selectedDotSet.value = linkedSetOf()
}
override fun getInput(): List<Any> {
- return _selectedDots.value.map(PatternDotViewModel::toCoordinate)
+ return selectedDotSet.value.map(PatternDotViewModel::toCoordinate)
}
private fun defaultDots(): List<PatternDotViewModel> {
@@ -204,6 +212,14 @@
max(min(outValue.float, 1f), MIN_DOT_HIT_FACTOR)
}
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ isInputEnabled: StateFlow<Boolean>,
+ onIntentionalUserInput: () -> Unit,
+ ): PatternBouncerViewModel
+ }
+
companion object {
private const val MIN_DOT_HIT_FACTOR = 0.2f
}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index aa447ff..cb36560 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -32,29 +32,34 @@
import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
import com.android.systemui.res.R
-import kotlinx.coroutines.CoroutineScope
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
/** Holds UI state and handles user input for the PIN code bouncer UI. */
-class PinBouncerViewModel(
+class PinBouncerViewModel
+@AssistedInject
+constructor(
applicationContext: Context,
- viewModelScope: CoroutineScope,
interactor: BouncerInteractor,
- isInputEnabled: StateFlow<Boolean>,
- private val onIntentionalUserInput: () -> Unit,
private val simBouncerInteractor: SimBouncerInteractor,
- authenticationMethod: AuthenticationMethodModel,
+ @Assisted isInputEnabled: StateFlow<Boolean>,
+ @Assisted private val onIntentionalUserInput: () -> Unit,
+ @Assisted override val authenticationMethod: AuthenticationMethodModel,
) :
AuthMethodBouncerViewModel(
- viewModelScope = viewModelScope,
interactor = interactor,
isInputEnabled = isInputEnabled,
) {
@@ -73,69 +78,89 @@
/** Currently entered pin keys. */
val pinInput: StateFlow<PinInputViewModel> = mutablePinInput
+ private val _hintedPinLength = MutableStateFlow<Int?>(null)
/** The length of the PIN for which we should show a hint. */
- val hintedPinLength: StateFlow<Int?> =
- if (isSimAreaVisible) {
- flowOf(null)
- } else {
- interactor.hintedPinLength
- }
- .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
+ val hintedPinLength: StateFlow<Int?> = _hintedPinLength.asStateFlow()
+ private val _backspaceButtonAppearance = MutableStateFlow(ActionButtonAppearance.Hidden)
/** Appearance of the backspace button. */
val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> =
- combine(
- mutablePinInput,
- interactor.isAutoConfirmEnabled,
- ) { mutablePinEntries, isAutoConfirmEnabled ->
- computeBackspaceButtonAppearance(
- pinInput = mutablePinEntries,
- isAutoConfirmEnabled = isAutoConfirmEnabled,
- )
- }
- .stateIn(
- scope = viewModelScope,
- // Make sure this is kept as WhileSubscribed or we can run into a bug where the
- // downstream continues to receive old/stale/cached values.
- started = SharingStarted.WhileSubscribed(),
- initialValue = ActionButtonAppearance.Hidden,
- )
+ _backspaceButtonAppearance.asStateFlow()
+ private val _confirmButtonAppearance = MutableStateFlow(ActionButtonAppearance.Hidden)
/** Appearance of the confirm button. */
val confirmButtonAppearance: StateFlow<ActionButtonAppearance> =
- interactor.isAutoConfirmEnabled
- .map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
- .stateIn(
- scope = viewModelScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = ActionButtonAppearance.Hidden,
- )
-
- override val authenticationMethod: AuthenticationMethodModel = authenticationMethod
+ _confirmButtonAppearance.asStateFlow()
override val lockoutMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
- init {
- viewModelScope.launch { simBouncerInteractor.subId.collect { onResetSimFlow() } }
+ private val requests = Channel<Request>(Channel.BUFFERED)
+
+ override suspend fun onActivated() {
+ coroutineScope {
+ launch { super.onActivated() }
+ launch {
+ requests.receiveAsFlow().collect { request ->
+ when (request) {
+ is OnErrorDialogDismissed -> {
+ simBouncerInteractor.onErrorDialogDismissed()
+ }
+ is OnAuthenticateButtonClickedForSim -> {
+ isSimUnlockingDialogVisible.value = true
+ simBouncerInteractor.verifySim(getInput())
+ isSimUnlockingDialogVisible.value = false
+ clearInput()
+ }
+ }
+ }
+ }
+ launch { simBouncerInteractor.subId.collect { onResetSimFlow() } }
+ launch {
+ if (isSimAreaVisible) {
+ flowOf(null)
+ } else {
+ interactor.hintedPinLength
+ }
+ .collectLatest { _hintedPinLength.value = it }
+ }
+ launch {
+ combine(
+ mutablePinInput,
+ interactor.isAutoConfirmEnabled,
+ ) { mutablePinEntries, isAutoConfirmEnabled ->
+ computeBackspaceButtonAppearance(
+ pinInput = mutablePinEntries,
+ isAutoConfirmEnabled = isAutoConfirmEnabled,
+ )
+ }
+ .collectLatest { _backspaceButtonAppearance.value = it }
+ }
+ launch {
+ interactor.isAutoConfirmEnabled
+ .map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
+ .collectLatest { _confirmButtonAppearance.value = it }
+ }
+ launch {
+ interactor.isPinEnhancedPrivacyEnabled
+ .map { !it }
+ .collectLatest { _isDigitButtonAnimationEnabled.value = it }
+ }
+ }
}
/** Notifies that the user dismissed the sim pin error dialog. */
fun onErrorDialogDismissed() {
- viewModelScope.launch { simBouncerInteractor.onErrorDialogDismissed() }
+ requests.trySend(OnErrorDialogDismissed)
}
+ private val _isDigitButtonAnimationEnabled =
+ MutableStateFlow(!interactor.isPinEnhancedPrivacyEnabled.value)
/**
* Whether the digit buttons should be animated when touched. Note that this doesn't affect the
* delete or enter buttons; those should always animate.
*/
val isDigitButtonAnimationEnabled: StateFlow<Boolean> =
- interactor.isPinEnhancedPrivacyEnabled
- .map { !it }
- .stateIn(
- scope = viewModelScope,
- started = SharingStarted.WhileSubscribed(),
- initialValue = !interactor.isPinEnhancedPrivacyEnabled.value,
- )
+ _isDigitButtonAnimationEnabled.asStateFlow()
/** Notifies that the user clicked on a PIN button with the given digit value. */
fun onPinButtonClicked(input: Int) {
@@ -163,19 +188,14 @@
/** Notifies that the user clicked the "enter" button. */
fun onAuthenticateButtonClicked() {
if (authenticationMethod == AuthenticationMethodModel.Sim) {
- viewModelScope.launch {
- isSimUnlockingDialogVisible.value = true
- simBouncerInteractor.verifySim(getInput())
- isSimUnlockingDialogVisible.value = false
- clearInput()
- }
+ requests.trySend(OnAuthenticateButtonClickedForSim)
} else {
tryAuthenticate(useAutoConfirm = false)
}
}
fun onDisableEsimButtonClicked() {
- viewModelScope.launch { simBouncerInteractor.disableEsim() }
+ simBouncerInteractor.disableEsim()
}
/** Resets the sim screen and shows a default message. */
@@ -242,6 +262,21 @@
else -> false
}
}
+
+ @AssistedFactory
+ interface Factory {
+ fun create(
+ isInputEnabled: StateFlow<Boolean>,
+ onIntentionalUserInput: () -> Unit,
+ authenticationMethod: AuthenticationMethodModel,
+ ): PinBouncerViewModel
+ }
+
+ private sealed interface Request
+
+ private data object OnErrorDialogDismissed : Request
+
+ private data object OnAuthenticateButtonClickedForSim : Request
}
/** Appearance of pin-pad action buttons. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
deleted file mode 100644
index e4bafcd..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.database.ContentObserver
-import android.net.Uri
-import android.os.Handler
-import android.os.UserHandle
-import android.provider.Settings
-import android.text.TextUtils
-import android.util.ArraySet
-import android.util.Log
-import androidx.annotation.GuardedBy
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.Dumpable
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.util.UserAwareController
-import com.android.systemui.util.settings.SecureSettings
-import java.io.PrintWriter
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-private const val TAG = "AutoAddTracker"
-private const val DELIMITER = ","
-
-/**
- * Class to track tiles that have been auto-added
- *
- * The list is backed by [Settings.Secure.QS_AUTO_ADDED_TILES].
- *
- * It also handles restore gracefully.
- */
-class AutoAddTracker
-@VisibleForTesting
-constructor(
- private val secureSettings: SecureSettings,
- private val broadcastDispatcher: BroadcastDispatcher,
- private val qsHost: QSHost,
- private val dumpManager: DumpManager,
- private val mainHandler: Handler?,
- private val backgroundExecutor: Executor,
- private var userId: Int
-) : UserAwareController, Dumpable {
-
- companion object {
- private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
- }
-
- @GuardedBy("autoAdded") private val autoAdded = ArraySet<String>()
- private var restoredTiles: Map<String, AutoTile>? = null
-
- override val currentUserId: Int
- get() = userId
-
- private val contentObserver =
- object : ContentObserver(mainHandler) {
- override fun onChange(
- selfChange: Boolean,
- uris: Collection<Uri>,
- flags: Int,
- _userId: Int
- ) {
- if (_userId != userId) {
- // Ignore changes outside of our user. We'll load the correct value on user
- // change
- return
- }
- loadTiles()
- }
- }
-
- private val restoreReceiver =
- object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action != Intent.ACTION_SETTING_RESTORED) return
- processRestoreIntent(intent)
- }
- }
-
- private fun processRestoreIntent(intent: Intent) {
- when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) {
- Settings.Secure.QS_TILES -> {
- restoredTiles =
- intent
- .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
- ?.split(DELIMITER)
- ?.mapIndexed(::AutoTile)
- ?.associateBy(AutoTile::tileType)
- ?: run {
- Log.w(TAG, "Null restored tiles for user $userId")
- emptyMap()
- }
- }
- Settings.Secure.QS_AUTO_ADDED_TILES -> {
- restoredTiles?.let { restoredTiles ->
- val restoredAutoAdded =
- intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)?.split(DELIMITER)
- ?: emptyList()
- val autoAddedBeforeRestore =
- intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)?.split(DELIMITER)
- ?: emptyList()
-
- val tilesToRemove = restoredAutoAdded.filter { it !in restoredTiles }
- if (tilesToRemove.isNotEmpty()) {
- Log.d(TAG, "Removing tiles: $tilesToRemove")
- qsHost.removeTiles(tilesToRemove)
- }
- val tiles =
- synchronized(autoAdded) {
- autoAdded.clear()
- autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore)
- getTilesFromListLocked()
- }
- saveTiles(tiles)
- }
- ?: run {
- Log.w(
- TAG,
- "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " +
- "${Settings.Secure.QS_TILES} for user $userId"
- )
- }
- }
- else -> {} // Do nothing for other Settings
- }
- }
-
- /** Init method must be called after construction to start listening */
- fun initialize() {
- dumpManager.registerDumpable(TAG, this)
- loadTiles()
- secureSettings.registerContentObserverForUserSync(
- secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES),
- contentObserver,
- UserHandle.USER_ALL
- )
- registerBroadcastReceiver()
- }
-
- /** Unregister listeners, receivers and observers */
- fun destroy() {
- dumpManager.unregisterDumpable(TAG)
- secureSettings.unregisterContentObserverSync(contentObserver)
- unregisterBroadcastReceiver()
- }
-
- private fun registerBroadcastReceiver() {
- broadcastDispatcher.registerReceiver(
- restoreReceiver,
- FILTER,
- backgroundExecutor,
- UserHandle.of(userId)
- )
- }
-
- private fun unregisterBroadcastReceiver() {
- broadcastDispatcher.unregisterReceiver(restoreReceiver)
- }
-
- override fun changeUser(newUser: UserHandle) {
- if (newUser.identifier == userId) return
- unregisterBroadcastReceiver()
- userId = newUser.identifier
- restoredTiles = null
- loadTiles()
- registerBroadcastReceiver()
- }
-
- fun getRestoredTilePosition(tile: String): Int =
- restoredTiles?.get(tile)?.index ?: QSHost.POSITION_AT_END
-
- /** Returns `true` if the tile has been auto-added before */
- fun isAdded(tile: String): Boolean {
- return synchronized(autoAdded) { tile in autoAdded }
- }
-
- /**
- * Sets a tile as auto-added.
- *
- * From here on, [isAdded] will return true for that tile.
- */
- fun setTileAdded(tile: String) {
- val tiles =
- synchronized(autoAdded) {
- if (autoAdded.add(tile)) {
- getTilesFromListLocked()
- } else {
- null
- }
- }
- tiles?.let { saveTiles(it) }
- }
-
- /**
- * Removes a tile from the list of auto-added.
- *
- * This allows for this tile to be auto-added again in the future.
- */
- fun setTileRemoved(tile: String) {
- val tiles =
- synchronized(autoAdded) {
- if (autoAdded.remove(tile)) {
- getTilesFromListLocked()
- } else {
- null
- }
- }
- tiles?.let { saveTiles(it) }
- }
-
- private fun getTilesFromListLocked(): String {
- return TextUtils.join(DELIMITER, autoAdded)
- }
-
- private fun saveTiles(tiles: String) {
- secureSettings.putStringForUser(
- Settings.Secure.QS_AUTO_ADDED_TILES,
- tiles,
- /* tag */ null,
- /* makeDefault */ false,
- userId,
- /* overrideableByRestore */ true
- )
- }
-
- private fun loadTiles() {
- synchronized(autoAdded) {
- autoAdded.clear()
- autoAdded.addAll(getAdded())
- }
- }
-
- private fun getAdded(): Collection<String> {
- val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId)
- return current?.split(DELIMITER) ?: emptySet()
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println("Current user: $userId")
- pw.println("Restored tiles: $restoredTiles")
- pw.println("Added tiles: $autoAdded")
- }
-
- @SysUISingleton
- class Builder
- @Inject
- constructor(
- private val secureSettings: SecureSettings,
- private val broadcastDispatcher: BroadcastDispatcher,
- private val qsHost: QSHost,
- private val dumpManager: DumpManager,
- @Main private val handler: Handler,
- @Background private val executor: Executor
- ) {
- private var userId: Int = 0
-
- fun setUserId(_userId: Int): Builder {
- userId = _userId
- return this
- }
-
- fun build(): AutoAddTracker {
- return AutoAddTracker(
- secureSettings,
- broadcastDispatcher,
- qsHost,
- dumpManager,
- handler,
- executor,
- userId
- )
- }
- }
-
- private data class AutoTile(val index: Int, val tileType: String)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
index c77233e..4323b31 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -47,12 +47,10 @@
class QSHostAdapter
@Inject
constructor(
- private val qsTileHost: QSTileHost,
private val interactor: CurrentTilesInteractor,
private val context: Context,
private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder,
@Application private val scope: CoroutineScope,
- flags: QSPipelineFlagsRepository,
dumpManager: DumpManager,
) : QSHost {
@@ -60,123 +58,69 @@
private const val TAG = "QSTileHost"
}
- private val useNewHost = flags.pipelineEnabled
-
@GuardedBy("callbacksMap") private val callbacksMap = mutableMapOf<QSHost.Callback, Job>()
init {
scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() }
// Redirect dump to the correct host (needed for CTS tests)
- dumpManager.registerCriticalDumpable(TAG, if (useNewHost) interactor else qsTileHost)
+ dumpManager.registerCriticalDumpable(TAG, interactor)
}
override fun getTiles(): Collection<QSTile> {
- return if (useNewHost) {
- interactor.currentQSTiles
- } else {
- qsTileHost.getTiles()
- }
+ return interactor.currentQSTiles
}
override fun getSpecs(): List<String> {
- return if (useNewHost) {
- interactor.currentTilesSpecs.map { it.spec }
- } else {
- qsTileHost.getSpecs()
- }
+ return interactor.currentTilesSpecs.map { it.spec }
}
override fun removeTile(spec: String) {
- if (useNewHost) {
- interactor.removeTiles(listOf(TileSpec.create(spec)))
- } else {
- qsTileHost.removeTile(spec)
- }
+ interactor.removeTiles(listOf(TileSpec.create(spec)))
}
override fun addCallback(callback: QSHost.Callback) {
- if (useNewHost) {
- val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } }
- synchronized(callbacksMap) { callbacksMap.put(callback, job) }
- } else {
- qsTileHost.addCallback(callback)
- }
+ val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } }
+ synchronized(callbacksMap) { callbacksMap.put(callback, job) }
}
override fun removeCallback(callback: QSHost.Callback) {
- if (useNewHost) {
- synchronized(callbacksMap) { callbacksMap.remove(callback)?.cancel() }
- } else {
- qsTileHost.removeCallback(callback)
- }
+ synchronized(callbacksMap) { callbacksMap.remove(callback)?.cancel() }
}
override fun removeTiles(specs: Collection<String>) {
- if (useNewHost) {
- interactor.removeTiles(specs.map(TileSpec::create))
- } else {
- qsTileHost.removeTiles(specs)
- }
+ interactor.removeTiles(specs.map(TileSpec::create))
}
override fun removeTileByUser(component: ComponentName) {
- if (useNewHost) {
- interactor.removeTiles(listOf(TileSpec.create(component)))
- } else {
- qsTileHost.removeTileByUser(component)
- }
+ interactor.removeTiles(listOf(TileSpec.create(component)))
}
override fun addTile(spec: String, position: Int) {
- if (useNewHost) {
- interactor.addTile(TileSpec.create(spec), position)
- } else {
- qsTileHost.addTile(spec, position)
- }
+ interactor.addTile(TileSpec.create(spec), position)
}
override fun addTile(component: ComponentName, end: Boolean) {
- if (useNewHost) {
- interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0)
- } else {
- qsTileHost.addTile(component, end)
- }
+ interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0)
}
override fun changeTilesByUser(previousTiles: List<String>, newTiles: List<String>) {
- if (useNewHost) {
- interactor.setTiles(newTiles.map(TileSpec::create))
- } else {
- qsTileHost.changeTilesByUser(previousTiles, newTiles)
- }
+ interactor.setTiles(newTiles.map(TileSpec::create))
}
override fun getContext(): Context {
- return if (useNewHost) {
- context
- } else {
- qsTileHost.context
- }
+ return context
}
override fun getUserContext(): Context {
- return if (useNewHost) {
- interactor.userContext.value
- } else {
- qsTileHost.userContext
- }
+ return interactor.userContext.value
}
override fun getUserId(): Int {
- return if (useNewHost) {
- interactor.userId.value
- } else {
- qsTileHost.userId
- }
+ return interactor.userId.value
}
override fun createTile(tileSpec: String): QSTile? {
- return qsTileHost.createTile(tileSpec)
+ return interactor.createTileSync(TileSpec.create(tileSpec))
}
override fun addTile(spec: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
deleted file mode 100644
index 03c2aa6..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ /dev/null
@@ -1,630 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dumpable;
-import com.android.systemui.ProtoDumpable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.nano.SystemUIProtoDump;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.plugins.qs.QSFactory;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.qs.external.CustomTileStatePersister;
-import com.android.systemui.qs.external.TileLifecycleManager;
-import com.android.systemui.qs.external.TileServiceKey;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.nano.QsTileState;
-import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
-import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
-import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
-import com.android.systemui.qs.tiles.di.NewQSTileFactory;
-import com.android.systemui.res.R;
-import com.android.systemui.settings.UserFileManager;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.settings.SecureSettings;
-
-import dagger.Lazy;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-import javax.inject.Inject;
-import javax.inject.Provider;
-
-/** Platform implementation of the quick settings tile host
- *
- * This class keeps track of the set of current tiles and is the in memory source of truth
- * (ground truth is kept in {@link Secure#QS_TILES}). When the ground truth changes,
- * {@link #onTuningChanged} will be called and the tiles will be re-created as needed.
- *
- * This class also provides the interface for adding/removing/changing tiles.
- */
-@SysUISingleton
-public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable,
- PanelInteractor, CustomTileAddedRepository {
- private static final String TAG = "QSTileHost";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- // Shared prefs that hold tile lifecycle info.
- @VisibleForTesting
- static final String TILES = "tiles_prefs";
-
- private final Context mContext;
- private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>();
- private final ArrayList<String> mTileSpecs = new ArrayList<>();
- private final TunerService mTunerService;
- private final PluginManager mPluginManager;
- private final QSLogger mQSLogger;
- private final CustomTileStatePersister mCustomTileStatePersister;
- private final Executor mMainExecutor;
- private final UserFileManager mUserFileManager;
-
- private final List<Callback> mCallbacks = new ArrayList<>();
- @Nullable
- private AutoTileManager mAutoTiles;
- private final ArrayList<QSFactory> mQsFactories = new ArrayList<>();
- private int mCurrentUser;
- private final Lazy<ShadeController> mShadeControllerProvider;
- private Context mUserContext;
- private UserTracker mUserTracker;
- private SecureSettings mSecureSettings;
- // Keep track of whether mTilesList contains the same information as the Settings value.
- // This is a performance optimization to reduce the number of blocking calls to Settings from
- // main thread.
- // This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged
- private boolean mTilesListDirty = true;
-
- private TileLifecycleManager.Factory mTileLifeCycleManagerFactory;
-
- private final QSPipelineFlagsRepository mFeatureFlags;
-
- @Inject
- public QSTileHost(Context context,
- Lazy<NewQSTileFactory> newQsTileFactoryProvider,
- QSFactory defaultFactory,
- @Main Executor mainExecutor,
- PluginManager pluginManager,
- TunerService tunerService,
- Provider<AutoTileManager> autoTiles,
- Lazy<ShadeController> shadeControllerProvider,
- QSLogger qsLogger,
- UserTracker userTracker,
- SecureSettings secureSettings,
- CustomTileStatePersister customTileStatePersister,
- TileLifecycleManager.Factory tileLifecycleManagerFactory,
- UserFileManager userFileManager,
- QSPipelineFlagsRepository featureFlags
- ) {
- mContext = context;
- mUserContext = context;
- mTunerService = tunerService;
- mPluginManager = pluginManager;
- mQSLogger = qsLogger;
- mMainExecutor = mainExecutor;
- mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
- mUserFileManager = userFileManager;
- mFeatureFlags = featureFlags;
-
- mShadeControllerProvider = shadeControllerProvider;
-
- if (featureFlags.getTilesEnabled()) {
- mQsFactories.add(newQsTileFactoryProvider.get());
- }
- mQsFactories.add(defaultFactory);
- pluginManager.addPluginListener(this, QSFactory.class, true);
- mUserTracker = userTracker;
- mCurrentUser = userTracker.getUserId();
- mSecureSettings = secureSettings;
- mCustomTileStatePersister = customTileStatePersister;
-
- mainExecutor.execute(() -> {
- // This is technically a hack to avoid circular dependency of
- // QSTileHost -> XXXTile -> QSTileHost. Posting ensures creation
- // finishes before creating any tiles.
- tunerService.addTunable(this, TILES_SETTING);
- // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
- if (!mFeatureFlags.getPipelineEnabled()) {
- mAutoTiles = autoTiles.get();
- }
- });
- }
-
- public void destroy() {
- mTiles.values().forEach(tile -> tile.destroy());
- mAutoTiles.destroy();
- mTunerService.removeTunable(this);
- mPluginManager.removePluginListener(this);
- }
-
- @Override
- public void onPluginConnected(QSFactory plugin, Context pluginContext) {
- // Give plugins priority over creation so they can override if they wish.
- mQsFactories.add(0, plugin);
- String value = mTunerService.getValue(TILES_SETTING);
- // Force remove and recreate of all tiles.
- onTuningChanged(TILES_SETTING, "");
- onTuningChanged(TILES_SETTING, value);
- }
-
- @Override
- public void onPluginDisconnected(QSFactory plugin) {
- mQsFactories.remove(plugin);
- // Force remove and recreate of all tiles.
- String value = mTunerService.getValue(TILES_SETTING);
- onTuningChanged(TILES_SETTING, "");
- onTuningChanged(TILES_SETTING, value);
- }
-
- @Override
- public void addCallback(Callback callback) {
- mCallbacks.add(callback);
- }
-
- @Override
- public void removeCallback(Callback callback) {
- mCallbacks.remove(callback);
- }
-
- @Override
- public Collection<QSTile> getTiles() {
- return mTiles.values();
- }
-
- @Override
- public void collapsePanels() {
- mShadeControllerProvider.get().postAnimateCollapseShade();
- }
-
- @Override
- public void forceCollapsePanels() {
- mShadeControllerProvider.get().postAnimateForceCollapseShade();
- }
-
- @Override
- public void openPanels() {
- mShadeControllerProvider.get().postAnimateExpandQs();
- }
-
- @Override
- public Context getContext() {
- return mContext;
- }
-
- @Override
- public Context getUserContext() {
- return mUserContext;
- }
-
- @Override
- public int getUserId() {
- return mCurrentUser;
- }
-
- public int indexOf(String spec) {
- return mTileSpecs.indexOf(spec);
- }
-
- /**
- * Whenever the Secure Setting keeping track of the current tiles changes (or upon start) this
- * will be called with the new value of the setting.
- *
- * This method will do the following:
- * <ol>
- * <li>Destroy any existing tile that's not one of the current tiles (in the setting)</li>
- * <li>Create new tiles for those that don't already exist. If this tiles end up being
- * not available, they'll also be destroyed.</li>
- * <li>Save the resolved list of tiles (current tiles that are available) into the setting.
- * This means that after this call ends, the tiles in the Setting, {@link #mTileSpecs},
- * and visible tiles ({@link #mTiles}) must match.
- * </li>
- * </ol>
- *
- * Additionally, if the user has changed, it'll do the following:
- * <ul>
- * <li>Change the user for SystemUI tiles: {@link QSTile#userSwitch}.</li>
- * <li>Destroy any {@link CustomTile} and recreate it for the new user.</li>
- * </ul>
- *
- * This happens in main thread as {@link com.android.systemui.tuner.TunerServiceImpl} dispatches
- * in main thread.
- *
- * @see QSTile#isAvailable
- */
- @MainThread
- @Override
- public void onTuningChanged(String key, String newValue) {
- if (!TILES_SETTING.equals(key)) {
- return;
- }
- int currentUser = mUserTracker.getUserId();
- if (currentUser != mCurrentUser) {
- mUserContext = mUserTracker.getUserContext();
- if (mAutoTiles != null) {
- mAutoTiles.changeUser(UserHandle.of(currentUser));
- }
- }
- // Do not process tiles if the flag is enabled.
- if (mFeatureFlags.getPipelineEnabled()) {
- return;
- }
- QSPipelineFlagsRepository.Utils.assertInLegacyMode();
- if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
- newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
- }
- final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
- if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
- Log.d(TAG, "Recreating tiles: " + tileSpecs);
- mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
- tile -> {
- Log.d(TAG, "Destroying tile: " + tile.getKey());
- mQSLogger.logTileDestroyed(tile.getKey(), "Tile removed");
- tile.getValue().destroy();
- });
- final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();
- for (String tileSpec : tileSpecs) {
- QSTile tile = mTiles.get(tileSpec);
- if (tile != null && (!(tile instanceof CustomTile)
- || ((CustomTile) tile).getUser() == currentUser)) {
- if (tile.isAvailable()) {
- Log.d(TAG, "Adding " + tile);
- tile.removeCallbacks();
- if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) {
- tile.userSwitch(currentUser);
- }
- newTiles.put(tileSpec, tile);
- mQSLogger.logTileAdded(tileSpec);
- } else {
- tile.destroy();
- Log.d(TAG, "Destroying not available tile: " + tileSpec);
- mQSLogger.logTileDestroyed(tileSpec, "Tile not available");
- }
- } else {
- // This means that the tile is a CustomTile AND the user is different, so let's
- // destroy it
- if (tile != null) {
- tile.destroy();
- Log.d(TAG, "Destroying tile for wrong user: " + tileSpec);
- mQSLogger.logTileDestroyed(tileSpec, "Tile for wrong user");
- }
- Log.d(TAG, "Creating tile: " + tileSpec);
- try {
- tile = createTile(tileSpec);
- if (tile != null) {
- if (tile.isAvailable()) {
- newTiles.put(tileSpec, tile);
- mQSLogger.logTileAdded(tileSpec);
- } else {
- tile.destroy();
- Log.d(TAG, "Destroying not available tile: " + tileSpec);
- mQSLogger.logTileDestroyed(tileSpec, "Tile not available");
- }
- } else {
- Log.d(TAG, "No factory for a spec: " + tileSpec);
- }
- } catch (Throwable t) {
- Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
- }
- }
- }
- mCurrentUser = currentUser;
- List<String> currentSpecs = new ArrayList<>(mTileSpecs);
- mTileSpecs.clear();
- mTileSpecs.addAll(newTiles.keySet()); // Only add the valid (available) tiles.
- mTiles.clear();
- mTiles.putAll(newTiles);
- if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
- // If we didn't manage to create any tiles, set it to empty (default)
- Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
- changeTilesByUser(currentSpecs, loadTileSpecs(mContext, ""));
- } else {
- String resolvedTiles = TextUtils.join(",", mTileSpecs);
- if (!resolvedTiles.equals(newValue)) {
- // If the resolved tiles (those we actually ended up with) are different than
- // the ones that are in the setting, update the Setting.
- saveTilesToSettings(mTileSpecs);
- }
- mTilesListDirty = false;
- for (int i = 0; i < mCallbacks.size(); i++) {
- mCallbacks.get(i).onTilesChanged();
- }
- }
- }
-
- /**
- * Only use with [CustomTile] if the tile doesn't exist anymore (and therefore doesn't need
- * its lifecycle terminated).
- */
- @Override
- public void removeTile(String spec) {
- if (spec.startsWith(CustomTile.PREFIX)) {
- // If the tile is removed (due to it not actually existing), mark it as removed. That
- // way it will be marked as newly added if it appears in the future.
- setTileAdded(CustomTile.getComponentFromSpec(spec), mCurrentUser, false);
- }
- mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)));
- }
-
- /**
- * Remove many tiles at once.
- *
- * It will only save to settings once (as opposed to {@link QSTileHost#removeTileByUser} called
- * multiple times).
- */
- @Override
- public void removeTiles(Collection<String> specs) {
- mMainExecutor.execute(() -> changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs)));
- }
-
- /**
- * Add a tile to the end
- *
- * @param spec string matching a pre-defined tilespec
- */
- public void addTile(String spec) {
- addTile(spec, POSITION_AT_END);
- }
-
- @Override
- public void addTile(String spec, int requestPosition) {
- mMainExecutor.execute(() ->
- changeTileSpecs(tileSpecs -> {
- if (tileSpecs.contains(spec)) return false;
-
- int size = tileSpecs.size();
- if (requestPosition == POSITION_AT_END || requestPosition >= size) {
- tileSpecs.add(spec);
- } else {
- tileSpecs.add(requestPosition, spec);
- }
- return true;
- })
- );
- }
-
- // When calling this, you may want to modify mTilesListDirty accordingly.
- @MainThread
- private void saveTilesToSettings(List<String> tileSpecs) {
- Log.d(TAG, "Saving tiles: " + tileSpecs + " for user: " + mCurrentUser);
- mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
- null /* tag */, false /* default */, mCurrentUser,
- true /* overrideable by restore */);
- }
-
- @MainThread
- private void changeTileSpecs(Predicate<List<String>> changeFunction) {
- final List<String> tileSpecs;
- if (!mTilesListDirty) {
- tileSpecs = new ArrayList<>(mTileSpecs);
- } else {
- tileSpecs = loadTileSpecs(mContext,
- mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser));
- }
- if (changeFunction.test(tileSpecs)) {
- mTilesListDirty = true;
- saveTilesToSettings(tileSpecs);
- }
- }
-
- @Override
- public void addTile(ComponentName tile) {
- addTile(tile, /* end */ false);
- }
-
- @Override
- public void addTile(ComponentName tile, boolean end) {
- String spec = CustomTile.toSpec(tile);
- addTile(spec, end ? POSITION_AT_END : 0);
- }
-
- /**
- * This will call through {@link #changeTilesByUser}. It should only be used when a tile is
- * removed by a <b>user action</b> like {@code adb}.
- */
- @Override
- public void removeTileByUser(ComponentName tile) {
- mMainExecutor.execute(() -> {
- List<String> newSpecs = new ArrayList<>(mTileSpecs);
- if (newSpecs.remove(CustomTile.toSpec(tile))) {
- changeTilesByUser(mTileSpecs, newSpecs);
- }
- });
- }
-
- /**
- * Change the tiles triggered by the user editing.
- * <p>
- * This is not called on device start, or on user change.
- *
- * {@link android.service.quicksettings.TileService#onTileRemoved} will be called for tiles
- * that are removed.
- */
- @MainThread
- @Override
- public void changeTilesByUser(List<String> previousTiles, List<String> newTiles) {
- final List<String> copy = new ArrayList<>(previousTiles);
- final int NP = copy.size();
- for (int i = 0; i < NP; i++) {
- String tileSpec = copy.get(i);
- if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
- if (!newTiles.contains(tileSpec)) {
- ComponentName component = CustomTile.getComponentFromSpec(tileSpec);
- Intent intent = new Intent().setComponent(component);
- TileLifecycleManager lifecycleManager = mTileLifeCycleManagerFactory.create(
- intent, new UserHandle(mCurrentUser));
- lifecycleManager.onStopListening();
- lifecycleManager.onTileRemoved();
- mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser));
- setTileAdded(component, mCurrentUser, false);
- lifecycleManager.flushMessagesAndUnbind();
- }
- }
- Log.d(TAG, "saveCurrentTiles " + newTiles);
- mTilesListDirty = true;
- saveTilesToSettings(newTiles);
- }
-
- @Nullable
- @Override
- public QSTile createTile(String tileSpec) {
- for (int i = 0; i < mQsFactories.size(); i++) {
- QSTile t = mQsFactories.get(i).createTile(tileSpec);
- if (t != null) {
- return t;
- }
- }
- return null;
- }
-
- /**
- * Check if a particular {@link CustomTile} has been added for a user and has not been removed
- * since.
- * @param componentName the {@link ComponentName} of the
- * {@link android.service.quicksettings.TileService} associated with the
- * tile.
- * @param userId the user to check
- */
- @Override
- public boolean isTileAdded(ComponentName componentName, int userId) {
- return mUserFileManager
- .getSharedPreferences(TILES, 0, userId)
- .getBoolean(componentName.flattenToString(), false);
- }
-
- /**
- * Persists whether a particular {@link CustomTile} has been added and it's currently in the
- * set of selected tiles ({@link #mTiles}.
- * @param componentName the {@link ComponentName} of the
- * {@link android.service.quicksettings.TileService} associated
- * with the tile.
- * @param userId the user for this tile
- * @param added {@code true} if the tile is being added, {@code false} otherwise
- */
- @Override
- public void setTileAdded(ComponentName componentName, int userId, boolean added) {
- mUserFileManager.getSharedPreferences(TILES, 0, userId)
- .edit()
- .putBoolean(componentName.flattenToString(), added)
- .apply();
- }
-
- @Override
- public List<String> getSpecs() {
- return mTileSpecs;
- }
-
- protected static List<String> loadTileSpecs(Context context, String tileList) {
- final Resources res = context.getResources();
-
- if (TextUtils.isEmpty(tileList)) {
- tileList = res.getString(R.string.quick_settings_tiles);
- Log.d(TAG, "Loaded tile specs from default config: " + tileList);
- } else {
- Log.d(TAG, "Loaded tile specs from setting: " + tileList);
- }
- final ArrayList<String> tiles = new ArrayList<String>();
- boolean addedDefault = false;
- Set<String> addedSpecs = new ArraySet<>();
- for (String tile : tileList.split(",")) {
- tile = tile.trim();
- if (tile.isEmpty()) continue;
- if (tile.equals("default")) {
- if (!addedDefault) {
- List<String> defaultSpecs = QSHost.getDefaultSpecs(context.getResources());
- for (String spec : defaultSpecs) {
- if (!addedSpecs.contains(spec)) {
- tiles.add(spec);
- addedSpecs.add(spec);
- }
- }
- addedDefault = true;
- }
- } else {
- if (!addedSpecs.contains(tile)) {
- tiles.add(tile);
- addedSpecs.add(tile);
- }
- }
- }
-
- if (!tiles.contains("internet")) {
- if (tiles.contains("wifi")) {
- // Replace the WiFi with Internet, and remove the Cell
- tiles.set(tiles.indexOf("wifi"), "internet");
- tiles.remove("cell");
- } else if (tiles.contains("cell")) {
- // Replace the Cell with Internet
- tiles.set(tiles.indexOf("cell"), "internet");
- }
- } else {
- tiles.remove("wifi");
- tiles.remove("cell");
- }
- return tiles;
- }
-
- @Override
- public void dump(PrintWriter pw, String[] args) {
- pw.println("QSTileHost:");
- pw.println("tile specs: " + mTileSpecs);
- pw.println("current user: " + mCurrentUser);
- pw.println("is dirty: " + mTilesListDirty);
- pw.println("tiles:");
- mTiles.values().stream().filter(obj -> obj instanceof Dumpable)
- .forEach(o -> ((Dumpable) o).dump(pw, args));
- }
-
- @Override
- public void dumpProto(@NotNull SystemUIProtoDump systemUIProtoDump, @NotNull String[] args) {
- List<QsTileState> data = mTiles.values().stream()
- .map(QSTile::getState)
- .map(TileStateToProtoKt::toProto)
- .filter(Objects::nonNull)
- .collect(Collectors.toList());
-
- systemUIProtoDump.tiles = data.toArray(new QsTileState[0]);
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
index 496a6f8..a947d36 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -18,17 +18,14 @@
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.QSHostAdapter
-import com.android.systemui.qs.QSTileHost
import com.android.systemui.qs.QsEventLogger
import com.android.systemui.qs.QsEventLoggerImpl
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractorImpl
-import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
import dagger.Binds
import dagger.Module
-import dagger.Provides
@Module
interface QSHostModule {
@@ -37,36 +34,10 @@
@Binds fun provideEventLogger(impl: QsEventLoggerImpl): QsEventLogger
- @Module
- companion object {
- private const val MAX_QS_INSTANCE_ID = 1 shl 20
+ @Binds fun providePanelInteractor(impl: PanelInteractorImpl): PanelInteractor
- @Provides
- @JvmStatic
- fun providePanelInteractor(
- featureFlags: QSPipelineFlagsRepository,
- qsHost: QSTileHost,
- panelInteractorImpl: PanelInteractorImpl
- ): PanelInteractor {
- return if (featureFlags.pipelineEnabled) {
- panelInteractorImpl
- } else {
- qsHost
- }
- }
-
- @Provides
- @JvmStatic
- fun provideCustomTileAddedRepository(
- featureFlags: QSPipelineFlagsRepository,
- qsHost: QSTileHost,
- customTileAddedRepository: CustomTileAddedSharedPrefsRepository
- ): CustomTileAddedRepository {
- return if (featureFlags.pipelineEnabled) {
- customTileAddedRepository
- } else {
- qsHost
- }
- }
- }
+ @Binds
+ fun provideCustomTileAddedRepository(
+ impl: CustomTileAddedSharedPrefsRepository
+ ): CustomTileAddedRepository
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index b705a03..29bcad4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -16,17 +16,7 @@
package com.android.systemui.qs.dagger;
-import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
-
-import android.content.Context;
-import android.os.Handler;
-
-import com.android.systemui.dagger.NightDisplayListenerModule;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.dagger.MediaModule;
-import com.android.systemui.qs.AutoAddTracker;
-import com.android.systemui.qs.QSHost;
import com.android.systemui.qs.ReduceBrightColorsController;
import com.android.systemui.qs.ReduceBrightColorsControllerImpl;
import com.android.systemui.qs.external.QSExternalModule;
@@ -36,24 +26,12 @@
import com.android.systemui.qs.tiles.di.QSTilesModule;
import com.android.systemui.qs.ui.adapter.QSSceneAdapter;
import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.ManagedProfileController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.DeviceControlsController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.SafetyController;
-import com.android.systemui.statusbar.policy.WalletController;
-import com.android.systemui.util.settings.SecureSettings;
-
-import dagger.Binds;
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.Multibinds;
import java.util.Map;
-import javax.inject.Named;
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.Multibinds;
/**
* Module for QS dependencies
@@ -78,45 +56,6 @@
@Multibinds
Map<String, QSTileImpl<?>> tileMap();
- @Provides
- @SysUISingleton
- static AutoTileManager provideAutoTileManager(
- Context context,
- AutoAddTracker.Builder autoAddTrackerBuilder,
- QSHost host,
- @Background Handler handler,
- SecureSettings secureSettings,
- HotspotController hotspotController,
- DataSaverController dataSaverController,
- ManagedProfileController managedProfileController,
- NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
- CastController castController,
- ReduceBrightColorsController reduceBrightColorsController,
- DeviceControlsController deviceControlsController,
- WalletController walletController,
- SafetyController safetyController,
- @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
- AutoTileManager manager = new AutoTileManager(
- context,
- autoAddTrackerBuilder,
- host,
- handler,
- secureSettings,
- hotspotController,
- dataSaverController,
- managedProfileController,
- nightDisplayListenerBuilder,
- castController,
- reduceBrightColorsController,
- deviceControlsController,
- walletController,
- safetyController,
- isReduceBrightColorsAvailable
- );
- manager.init();
- return manager;
- }
-
@Binds
QSSceneAdapter bindsQsSceneInteractor(QSSceneAdapterImpl impl);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 02379e6..4a96710 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -116,6 +116,8 @@
*/
fun setTiles(specs: List<TileSpec>)
+ fun createTileSync(spec: TileSpec): QSTile?
+
companion object {
val POSITION_AT_END: Int = TileSpecRepository.POSITION_AT_END
}
@@ -190,9 +192,7 @@
}
init {
- if (featureFlags.pipelineEnabled) {
- startTileCollection()
- }
+ startTileCollection()
}
private fun startTileCollection() {
@@ -342,15 +342,16 @@
lifecycleManager.flushMessagesAndUnbind()
}
+ override fun createTileSync(spec: TileSpec): QSTile? {
+ return if (featureFlags.tilesEnabled) {
+ newQSTileFactory.get().createTile(spec.spec)
+ } else {
+ null
+ } ?: tileFactory.createTile(spec.spec)
+ }
+
private suspend fun createTile(spec: TileSpec): QSTile? {
- val tile =
- withContext(mainDispatcher) {
- if (featureFlags.tilesEnabled) {
- newQSTileFactory.get().createTile(spec.spec)
- } else {
- null
- } ?: tileFactory.createTile(spec.spec)
- }
+ val tile = withContext(mainDispatcher) { createTileSync(spec) }
if (tile == null) {
logger.logTileNotFoundInFactory(spec)
return null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
index c8fbeb5..0bcb6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -40,14 +40,12 @@
) : CoreStartable {
override fun start() {
- if (featureFlags.pipelineEnabled) {
- accessibilityTilesInteractor.init(currentTilesInteractor)
- autoAddInteractor.init(currentTilesInteractor)
- restoreReconciliationInteractor.start()
+ accessibilityTilesInteractor.init(currentTilesInteractor)
+ autoAddInteractor.init(currentTilesInteractor)
+ restoreReconciliationInteractor.start()
- if (NewQsUI.isEnabled) {
- gridConsistencyInteractor.start()
- }
+ if (NewQsUI.isEnabled) {
+ gridConsistencyInteractor.start()
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
index 42bee3c..5dc8d1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
@@ -9,19 +9,10 @@
@SysUISingleton
class QSPipelineFlagsRepository @Inject constructor() {
- val pipelineEnabled: Boolean
- get() = AconfigFlags.qsNewPipeline()
-
val tilesEnabled: Boolean
get() = AconfigFlags.qsNewTiles()
companion object Utils {
- fun assertInLegacyMode() =
- RefactorFlagUtils.assertInLegacyMode(
- AconfigFlags.qsNewPipeline(),
- AconfigFlags.FLAG_QS_NEW_PIPELINE
- )
-
fun assertNewTiles() =
RefactorFlagUtils.assertInNewMode(
AconfigFlags.qsNewTiles(),
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
deleted file mode 100644
index a538856..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ /dev/null
@@ -1,520 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
-
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.hardware.display.ColorDisplayManager;
-import android.hardware.display.NightDisplayListener;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.NightDisplayListenerModule;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.AutoAddTracker;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.UserSettingObserver;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.CastDevice;
-import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.DataSaverController.Listener;
-import com.android.systemui.statusbar.policy.DeviceControlsController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.HotspotController.Callback;
-import com.android.systemui.statusbar.policy.SafetyController;
-import com.android.systemui.statusbar.policy.WalletController;
-import com.android.systemui.util.UserAwareController;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Objects;
-
-import javax.inject.Named;
-
-/**
- * Manages which tiles should be automatically added to QS.
- */
-public class AutoTileManager implements UserAwareController {
- private static final String TAG = "AutoTileManager";
-
- public static final String HOTSPOT = "hotspot";
- public static final String SAVER = "saver";
- public static final String INVERSION = "inversion";
- public static final String WORK = "work";
- public static final String NIGHT = "night";
- public static final String CAST = "cast";
- public static final String DEVICE_CONTROLS = "controls";
- public static final String WALLET = "wallet";
- public static final String BRIGHTNESS = "reduce_brightness";
- static final String SETTING_SEPARATOR = ":";
-
- private UserHandle mCurrentUser;
- private boolean mInitialized;
- private final String mSafetySpec;
-
- protected final Context mContext;
- protected final QSHost mHost;
- protected final Handler mHandler;
- protected final SecureSettings mSecureSettings;
- protected final AutoAddTracker mAutoTracker;
- private final HotspotController mHotspotController;
- private final DataSaverController mDataSaverController;
- private final ManagedProfileController mManagedProfileController;
- private final NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
- private NightDisplayListener mNightDisplayListener;
- private final CastController mCastController;
- private final DeviceControlsController mDeviceControlsController;
- private final WalletController mWalletController;
- private final ReduceBrightColorsController mReduceBrightColorsController;
- private final SafetyController mSafetyController;
- private final boolean mIsReduceBrightColorsAvailable;
- private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>();
-
- public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder,
- QSHost host,
- @Background Handler handler,
- SecureSettings secureSettings,
- HotspotController hotspotController,
- DataSaverController dataSaverController,
- ManagedProfileController managedProfileController,
- NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
- CastController castController,
- ReduceBrightColorsController reduceBrightColorsController,
- DeviceControlsController deviceControlsController,
- WalletController walletController,
- SafetyController safetyController,
- @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
- mContext = context;
- mHost = host;
- mSecureSettings = secureSettings;
- mCurrentUser = mHost.getUserContext().getUser();
- mAutoTracker = autoAddTrackerBuilder.setUserId(mCurrentUser.getIdentifier()).build();
- mHandler = handler;
- mHotspotController = hotspotController;
- mDataSaverController = dataSaverController;
- mManagedProfileController = managedProfileController;
- mNightDisplayListenerBuilder = nightDisplayListenerBuilder;
- mCastController = castController;
- mReduceBrightColorsController = reduceBrightColorsController;
- mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable;
- mDeviceControlsController = deviceControlsController;
- mWalletController = walletController;
- mSafetyController = safetyController;
- String safetySpecClass;
- try {
- safetySpecClass =
- context.getResources().getString(R.string.safety_quick_settings_tile_class);
- if (safetySpecClass.length() == 0) {
- safetySpecClass = null;
- }
- } catch (Resources.NotFoundException | NullPointerException e) {
- safetySpecClass = null;
- }
- mSafetySpec = safetySpecClass != null ? CustomTile.toSpec(new ComponentName(mContext
- .getPackageManager().getPermissionControllerPackageName(), safetySpecClass)) : null;
- }
-
- /**
- * Init method must be called after construction to start listening
- */
- public void init() {
- QSPipelineFlagsRepository.Utils.assertInLegacyMode();
- if (mInitialized) {
- Log.w(TAG, "Trying to re-initialize");
- return;
- }
- mAutoTracker.initialize();
- populateSettingsList();
- startControllersAndSettingsListeners();
- mInitialized = true;
- }
-
- protected void startControllersAndSettingsListeners() {
- if (!mAutoTracker.isAdded(HOTSPOT)) {
- mHotspotController.addCallback(mHotspotCallback);
- }
- if (!mAutoTracker.isAdded(SAVER)) {
- mDataSaverController.addCallback(mDataSaverListener);
- }
- mManagedProfileController.addCallback(mProfileCallback);
-
- mNightDisplayListener = mNightDisplayListenerBuilder
- .setUser(mCurrentUser.getIdentifier())
- .build();
- if (!mAutoTracker.isAdded(NIGHT)
- && ColorDisplayManager.isNightDisplayAvailable(mContext)) {
- mNightDisplayListener.setCallback(mNightDisplayCallback);
- }
- if (!mAutoTracker.isAdded(CAST)) {
- mCastController.addCallback(mCastCallback);
- }
- if (!mAutoTracker.isAdded(BRIGHTNESS) && mIsReduceBrightColorsAvailable) {
- mReduceBrightColorsController.addCallback(mReduceBrightColorsCallback);
- }
- // We always want this callback, because if the feature stops being supported,
- // we want to remove the tile from AutoAddTracker. That way it will be re-added when the
- // feature is reenabled (similar to work tile).
- mDeviceControlsController.setCallback(mDeviceControlsCallback);
- if (!mAutoTracker.isAdded(WALLET)) {
- initWalletController();
- }
- if (mSafetySpec != null) {
- if (!mAutoTracker.isAdded(mSafetySpec)) {
- initSafetyTile();
- }
- mSafetyController.addCallback(mSafetyCallback);
- }
-
- int settingsN = mAutoAddSettingList.size();
- for (int i = 0; i < settingsN; i++) {
- if (!mAutoTracker.isAdded(mAutoAddSettingList.get(i).mSpec)) {
- mAutoAddSettingList.get(i).setListening(true);
- }
- }
- }
-
- protected void stopListening() {
- mHotspotController.removeCallback(mHotspotCallback);
- mDataSaverController.removeCallback(mDataSaverListener);
- mManagedProfileController.removeCallback(mProfileCallback);
- if (ColorDisplayManager.isNightDisplayAvailable(mContext)
- && mNightDisplayListener != null) {
- mNightDisplayListener.setCallback(null);
- }
- if (mIsReduceBrightColorsAvailable) {
- mReduceBrightColorsController.removeCallback(mReduceBrightColorsCallback);
- }
- mCastController.removeCallback(mCastCallback);
- mDeviceControlsController.removeCallback();
- if (mSafetySpec != null) {
- mSafetyController.removeCallback(mSafetyCallback);
- }
- int settingsN = mAutoAddSettingList.size();
- for (int i = 0; i < settingsN; i++) {
- mAutoAddSettingList.get(i).setListening(false);
- }
- }
-
- public void destroy() {
- stopListening();
- mAutoTracker.destroy();
- }
-
- /**
- * Populates a list with the pairs setting:spec in the config resource.
- * <p>
- * This will only create {@link AutoAddSetting} objects for those tiles that have not been
- * auto-added before, and set the corresponding {@link ContentObserver} to listening.
- */
- private void populateSettingsList() {
- String [] autoAddList;
- try {
- autoAddList = mContext.getResources().getStringArray(
- R.array.config_quickSettingsAutoAdd);
- } catch (Resources.NotFoundException e) {
- Log.w(TAG, "Missing config resource");
- return;
- }
- // getStringArray returns @NotNull, so if we got here, autoAddList is not null
- for (String tile : autoAddList) {
- String[] split = tile.split(SETTING_SEPARATOR);
- if (split.length == 2) {
- String setting = split[0];
- String spec = split[1];
- // Populate all the settings. As they may not have been added in other users
- AutoAddSetting s = new AutoAddSetting(
- mSecureSettings, mHandler, setting, mCurrentUser.getIdentifier(), spec);
- mAutoAddSettingList.add(s);
- } else {
- Log.w(TAG, "Malformed item in array: " + tile);
- }
- }
- }
-
- /*
- * This will be sent off the main thread if needed
- */
- @Override
- public void changeUser(UserHandle newUser) {
- if (!mInitialized) {
- throw new IllegalStateException("AutoTileManager not initialized");
- }
- if (!Thread.currentThread().equals(mHandler.getLooper().getThread())) {
- mHandler.post(() -> changeUser(newUser));
- return;
- }
- if (newUser.getIdentifier() == mCurrentUser.getIdentifier()) {
- return;
- }
- stopListening();
- mCurrentUser = newUser;
- int settingsN = mAutoAddSettingList.size();
- for (int i = 0; i < settingsN; i++) {
- mAutoAddSettingList.get(i).setUserId(newUser.getIdentifier());
- }
- mAutoTracker.changeUser(newUser);
- startControllersAndSettingsListeners();
- }
-
- @Override
- public int getCurrentUserId() {
- return mCurrentUser.getIdentifier();
- }
-
- private final ManagedProfileController.Callback mProfileCallback =
- new ManagedProfileController.Callback() {
- @Override
- public void onManagedProfileChanged() {
- if (mManagedProfileController.hasActiveProfile()) {
- if (mAutoTracker.isAdded(WORK)) return;
- final int position = mAutoTracker.getRestoredTilePosition(WORK);
- mHost.addTile(WORK, position);
- mAutoTracker.setTileAdded(WORK);
- } else {
- if (!mAutoTracker.isAdded(WORK)) return;
- mHost.removeTile(WORK);
- mAutoTracker.setTileRemoved(WORK);
- }
- }
-
- @Override
- public void onManagedProfileRemoved() {
- }
- };
-
- private final DataSaverController.Listener mDataSaverListener = new Listener() {
- @Override
- public void onDataSaverChanged(boolean isDataSaving) {
- if (mAutoTracker.isAdded(SAVER)) return;
- if (isDataSaving) {
- mHost.addTile(SAVER);
- mAutoTracker.setTileAdded(SAVER);
- mHandler.post(() -> mDataSaverController.removeCallback(mDataSaverListener));
- }
- }
- };
-
- private final HotspotController.Callback mHotspotCallback = new Callback() {
- @Override
- public void onHotspotChanged(boolean enabled, int numDevices) {
- if (mAutoTracker.isAdded(HOTSPOT)) return;
- if (enabled) {
- mHost.addTile(HOTSPOT);
- mAutoTracker.setTileAdded(HOTSPOT);
- mHandler.post(() -> mHotspotController.removeCallback(mHotspotCallback));
- }
- }
- };
-
- private final DeviceControlsController.Callback mDeviceControlsCallback =
- new DeviceControlsController.Callback() {
- @Override
- public void onControlsUpdate(@Nullable Integer position) {
- if (mAutoTracker.isAdded(DEVICE_CONTROLS)) return;
- if (position != null && !hasTile(DEVICE_CONTROLS)) {
- mHost.addTile(DEVICE_CONTROLS, position);
- mAutoTracker.setTileAdded(DEVICE_CONTROLS);
- }
- mHandler.post(() -> mDeviceControlsController.removeCallback());
- }
-
- @Override
- public void removeControlsAutoTracker() {
- mAutoTracker.setTileRemoved(DEVICE_CONTROLS);
- }
- };
-
- private boolean hasTile(String tileSpec) {
- if (tileSpec == null) return false;
- Collection<QSTile> tiles = mHost.getTiles();
- for (QSTile tile : tiles) {
- if (tileSpec.equals(tile.getTileSpec())) {
- return true;
- }
- }
- return false;
- }
-
- private void initWalletController() {
- if (mAutoTracker.isAdded(WALLET)) return;
- Integer position = mWalletController.getWalletPosition();
-
- if (position != null) {
- mHost.addTile(WALLET, position);
- mAutoTracker.setTileAdded(WALLET);
- }
- }
-
- private void initSafetyTile() {
- if (mSafetySpec == null || mAutoTracker.isAdded(mSafetySpec)) {
- return;
- }
- mHost.addTile(CustomTile.getComponentFromSpec(mSafetySpec), true);
- mAutoTracker.setTileAdded(mSafetySpec);
- }
-
- @VisibleForTesting
- final NightDisplayListener.Callback mNightDisplayCallback =
- new NightDisplayListener.Callback() {
- @Override
- public void onActivated(boolean activated) {
- if (activated) {
- addNightTile();
- }
- }
-
- @Override
- public void onAutoModeChanged(int autoMode) {
- if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME
- || autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
- addNightTile();
- }
- }
-
- private void addNightTile() {
- if (mAutoTracker.isAdded(NIGHT)) return;
- mHost.addTile(NIGHT);
- mAutoTracker.setTileAdded(NIGHT);
- mHandler.post(() -> mNightDisplayListener.setCallback(null));
- }
- };
-
- @VisibleForTesting
- final ReduceBrightColorsController.Listener mReduceBrightColorsCallback =
- new ReduceBrightColorsController.Listener() {
- @Override
- public void onActivated(boolean activated) {
- if (activated) {
- addReduceBrightColorsTile();
- }
- }
-
- @Override
- public void onFeatureEnabledChanged(boolean enabled) {
- if (!enabled) {
- mHost.removeTile(BRIGHTNESS);
- mHandler.post(() -> mReduceBrightColorsController.removeCallback(this));
- }
- }
-
- private void addReduceBrightColorsTile() {
- if (mAutoTracker.isAdded(BRIGHTNESS)) return;
- mHost.addTile(BRIGHTNESS);
- mAutoTracker.setTileAdded(BRIGHTNESS);
- mHandler.post(() -> mReduceBrightColorsController.removeCallback(this));
- }
- };
-
- @VisibleForTesting
- final CastController.Callback mCastCallback = new CastController.Callback() {
- @Override
- public void onCastDevicesChanged() {
- if (mAutoTracker.isAdded(CAST)) return;
-
- boolean isCasting = false;
- for (CastDevice device : mCastController.getCastDevices()) {
- if (device.isCasting()) {
- isCasting = true;
- break;
- }
- }
-
- if (isCasting) {
- mHost.addTile(CAST);
- mAutoTracker.setTileAdded(CAST);
- mHandler.post(() -> mCastController.removeCallback(mCastCallback));
- }
- }
- };
-
- @VisibleForTesting
- final SafetyController.Listener mSafetyCallback = new SafetyController.Listener() {
- @Override
- public void onSafetyCenterEnableChanged(boolean isSafetyCenterEnabled) {
- if (mSafetySpec == null) {
- return;
- }
-
- if (isSafetyCenterEnabled && !mAutoTracker.isAdded(mSafetySpec)) {
- initSafetyTile();
- } else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) {
- mHost.removeTile(mSafetySpec);
- mAutoTracker.setTileRemoved(mSafetySpec);
- }
- }
- };
-
- @VisibleForTesting
- protected UserSettingObserver getSecureSettingForKey(String key) {
- for (UserSettingObserver s : mAutoAddSettingList) {
- if (Objects.equals(key, s.getKey())) {
- return s;
- }
- }
- return null;
- }
-
- /**
- * Tracks tiles that should be auto added when a setting changes.
- * <p>
- * When the setting changes to a value different from 0, if the tile has not been auto added
- * before, it will be added and the listener will be stopped.
- */
- private class AutoAddSetting extends UserSettingObserver {
- private final String mSpec;
-
- AutoAddSetting(
- SecureSettings secureSettings,
- Handler handler,
- String setting,
- int userId,
- String tileSpec
- ) {
- super(secureSettings, handler, setting, userId);
- mSpec = tileSpec;
- }
-
- @Override
- protected void handleValueChanged(int value, boolean observedChange) {
- if (mAutoTracker.isAdded(mSpec)) {
- // This should not be listening anymore
- mHandler.post(() -> setListening(false));
- return;
- }
- if (value != 0) {
- if (mSpec.startsWith(CustomTile.PREFIX)) {
- mHost.addTile(CustomTile.getComponentFromSpec(mSpec), /* end */ true);
- } else {
- mHost.addTile(mSpec);
- }
- mAutoTracker.setTileAdded(mSpec);
- mHandler.post(() -> setListening(false));
- }
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index 1224275..e29e069 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -21,13 +21,12 @@
import android.content.SharedPreferences
import android.provider.Settings
import android.util.Log
-import com.android.systemui.res.R
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.dagger.ControlsComponent
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
import com.android.systemui.settings.UserContextProvider
-import com.android.systemui.statusbar.phone.AutoTileManager
import com.android.systemui.statusbar.policy.DeviceControlsController.Callback
import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
@@ -35,14 +34,16 @@
/**
* Watches for Device Controls QS Tile activation, which can happen in two ways:
* <ol>
- * <li>Migration from Power Menu - For existing Android 11 users, create a tile in a high
- * priority position.
- * <li>Device controls service becomes available - For non-migrated users, create a tile and
- * place at the end of active tiles, and initiate seeding where possible.
+ * <li>Migration from Power Menu - For existing Android 11 users, create a tile in a high priority
+ * position.
+ * <li>Device controls service becomes available - For non-migrated users, create a tile and place
+ * at the end of active tiles, and initiate seeding where possible.
* </ol>
*/
@SysUISingleton
-public class DeviceControlsControllerImpl @Inject constructor(
+public class DeviceControlsControllerImpl
+@Inject
+constructor(
private val context: Context,
private val controlsComponent: ControlsComponent,
private val userContextProvider: UserContextProvider,
@@ -52,13 +53,14 @@
private var callback: Callback? = null
internal var position: Int? = null
- private val listingCallback = object : ControlsListingController.ControlsListingCallback {
- override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
- if (!serviceInfos.isEmpty()) {
- seedFavorites(serviceInfos)
+ private val listingCallback =
+ object : ControlsListingController.ControlsListingCallback {
+ override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+ if (!serviceInfos.isEmpty()) {
+ seedFavorites(serviceInfos)
+ }
}
}
- }
companion object {
private const val TAG = "DeviceControlsControllerImpl"
@@ -80,7 +82,7 @@
}
/**
- * This migration logic assumes that something like [AutoTileManager] is tracking state
+ * This migration logic assumes that something like [AutoAddTracker] is tracking state
* externally, and won't call this method after receiving a response via
* [Callback#onControlsUpdate], once per user. Otherwise the calculated position may be
* incorrect.
@@ -118,16 +120,19 @@
}
/**
- * See if any available control service providers match one of the preferred components. If
- * they do, and there are no current favorites for that component, query the preferred
- * component for a limited number of suggested controls.
+ * See if any available control service providers match one of the preferred components. If they
+ * do, and there are no current favorites for that component, query the preferred component for
+ * a limited number of suggested controls.
*/
private fun seedFavorites(serviceInfos: List<ControlsServiceInfo>) {
- val preferredControlsPackages = context.getResources().getStringArray(
- R.array.config_controlsPreferredPackages)
+ val preferredControlsPackages =
+ context.getResources().getStringArray(R.array.config_controlsPreferredPackages)
- val prefs = userContextProvider.userContext.getSharedPreferences(
- PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
+ val prefs =
+ userContextProvider.userContext.getSharedPreferences(
+ PREFS_CONTROLS_FILE,
+ Context.MODE_PRIVATE
+ )
val seededPackages =
prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet()
@@ -157,21 +162,22 @@
if (componentsToSeed.isEmpty()) return
controlsController.seedFavoritesForComponents(
- componentsToSeed,
- { response ->
- Log.d(TAG, "Controls seeded: $response")
- if (response.accepted) {
- addPackageToSeededSet(prefs, response.packageName)
- if (position == null) {
- position = QS_DEFAULT_POSITION
- }
- fireControlsUpdate()
-
- controlsComponent.getControlsListingController().ifPresent {
- it.removeCallback(listingCallback)
- }
+ componentsToSeed,
+ { response ->
+ Log.d(TAG, "Controls seeded: $response")
+ if (response.accepted) {
+ addPackageToSeededSet(prefs, response.packageName)
+ if (position == null) {
+ position = QS_DEFAULT_POSITION
}
- })
+ fireControlsUpdate()
+
+ controlsComponent.getControlsListingController().ifPresent {
+ it.removeCallback(listingCallback)
+ }
+ }
+ }
+ )
}
private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
index a4936e6..8e215f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
@@ -33,9 +33,10 @@
import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.ui.BouncerDialogFactory
import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
-import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModelFactory
import com.android.systemui.flags.Flags
import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.lifecycle.rememberViewModel
import com.android.systemui.motion.createSysUiComposeMotionTestRule
import com.android.systemui.scene.domain.startable.sceneContainerStartable
import com.android.systemui.testKosmos
@@ -81,7 +82,8 @@
private fun BouncerContentUnderTest() {
PlatformTheme {
BouncerContent(
- viewModel = kosmos.bouncerViewModel,
+ viewModel =
+ rememberViewModel { kosmos.bouncerSceneContentViewModelFactory.create() },
layout = BouncerSceneLayout.BESIDE_USER_SWITCHER,
modifier = Modifier.fillMaxSize().testTag("BouncerContent"),
dialogFactory = bouncerDialogFactory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
index 2948c02..4b61a0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
@@ -24,14 +24,14 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.LargeTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.bouncer.ui.viewmodel.patternBouncerViewModelFactory
+import com.android.systemui.lifecycle.activateIn
import com.android.systemui.motion.createSysUiComposeMotionTestRule
import com.android.systemui.testKosmos
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.takeWhile
+import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@@ -51,15 +51,15 @@
@get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos)
- private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
- private val viewModel by lazy {
- PatternBouncerViewModel(
- applicationContext = context,
- viewModelScope = kosmos.testScope.backgroundScope,
- interactor = bouncerInteractor,
+ private val viewModel =
+ kosmos.patternBouncerViewModelFactory.create(
isInputEnabled = MutableStateFlow(true).asStateFlow(),
onIntentionalUserInput = {},
)
+
+ @Before
+ fun setUp() {
+ viewModel.activateIn(motionTestRule.toolkit.testScope)
}
@Composable
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
deleted file mode 100644
index 1eeaef7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.util.settings.FakeSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-@RunWith(AndroidJUnit4.class)
-@RunWithLooper
-@SmallTest
-public class AutoAddTrackerTest extends SysuiTestCase {
-
- private static final int END_POSITION = -1;
- private static final int USER = 0;
-
- @Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
- private QSHost mQSHost;
- @Mock
- private DumpManager mDumpManager;
- @Captor
- private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
- @Captor
- private ArgumentCaptor<IntentFilter> mIntentFilterArgumentCaptor;
-
- private Executor mBackgroundExecutor = Runnable::run; // Direct executor
- private AutoAddTracker mAutoTracker;
- private SecureSettings mSecureSettings;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mSecureSettings = new FakeSettings();
-
- mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, null, USER);
-
- mAutoTracker = createAutoAddTracker(USER);
- mAutoTracker.initialize();
- }
-
- @Test
- public void testChangeFromBackup() {
- assertFalse(mAutoTracker.isAdded(SAVER));
-
- mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, SAVER, USER);
-
- assertTrue(mAutoTracker.isAdded(SAVER));
-
- mAutoTracker.destroy();
- }
-
- @Test
- public void testSetAdded() {
- assertFalse(mAutoTracker.isAdded(SAVER));
- mAutoTracker.setTileAdded(SAVER);
-
- assertTrue(mAutoTracker.isAdded(SAVER));
-
- mAutoTracker.destroy();
- }
-
- @Test
- public void testPersist() {
- assertFalse(mAutoTracker.isAdded(SAVER));
- mAutoTracker.setTileAdded(SAVER);
-
- mAutoTracker.destroy();
- mAutoTracker = createAutoAddTracker(USER);
- mAutoTracker.initialize();
-
- assertTrue(mAutoTracker.isAdded(SAVER));
-
- mAutoTracker.destroy();
- }
-
- @Test
- public void testIndependentUsers() {
- mAutoTracker.setTileAdded(SAVER);
-
- mAutoTracker = createAutoAddTracker(USER + 1);
- mAutoTracker.initialize();
- assertFalse(mAutoTracker.isAdded(SAVER));
- }
-
- @Test
- public void testChangeUser() {
- mAutoTracker.setTileAdded(SAVER);
-
- mAutoTracker = createAutoAddTracker(USER + 1);
- mAutoTracker.changeUser(UserHandle.of(USER));
- assertTrue(mAutoTracker.isAdded(SAVER));
- }
-
- @Test
- public void testRestoredTilePositionPreserved() {
- verify(mBroadcastDispatcher).registerReceiver(
- mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
- String restoredTiles = "saver,internet,work,cast";
- Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
-
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
- assertEquals(2, mAutoTracker.getRestoredTilePosition("work"));
- }
-
- @Test
- public void testNoRestoredTileReturnsEndPosition() {
- verify(mBroadcastDispatcher).registerReceiver(
- mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
- Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, null);
-
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
- assertEquals(END_POSITION, mAutoTracker.getRestoredTilePosition("work"));
- }
-
- @Test
- public void testBroadcastReceiverRegistered() {
- verify(mBroadcastDispatcher).registerReceiver(
- any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER)),
- anyInt(), any());
-
- assertTrue(
- mIntentFilterArgumentCaptor.getValue().hasAction(Intent.ACTION_SETTING_RESTORED));
- }
-
- @Test
- public void testBroadcastReceiverChangesWithUser() {
- mAutoTracker.changeUser(UserHandle.of(USER + 1));
-
- InOrder inOrder = Mockito.inOrder(mBroadcastDispatcher);
- inOrder.verify(mBroadcastDispatcher).unregisterReceiver(any());
- inOrder.verify(mBroadcastDispatcher)
- .registerReceiver(any(), any(), any(), eq(UserHandle.of(USER + 1)), anyInt(),
- any());
- }
-
- @Test
- public void testSettingRestoredWithTilesNotRemovedInSource_noAutoAddedInTarget() {
- verify(mBroadcastDispatcher).registerReceiver(
- mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
-
- // These tiles were present in the original device
- String restoredTiles = "saver,work,internet,cast";
- Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
- // And these tiles have been auto-added in the original device
- // (no auto-added before restore)
- String restoredAutoAddTiles = "work";
- Intent restoreAutoAddTilesIntent =
- makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
-
- // Then, don't remove any current tiles
- verify(mQSHost, never()).removeTiles(any());
- assertEquals(restoredAutoAddTiles,
- mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
- }
-
- @Test
- public void testSettingRestoredWithTilesRemovedInSource_noAutoAddedInTarget() {
- verify(mBroadcastDispatcher)
- .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
- anyInt(), any());
-
- // These tiles were present in the original device
- String restoredTiles = "saver,internet,cast";
- Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
- // And these tiles have been auto-added in the original device
- // (no auto-added before restore)
- String restoredAutoAddTiles = "work";
- Intent restoreAutoAddTilesIntent =
- makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
-
- // Then, remove work tile
- verify(mQSHost).removeTiles(List.of("work"));
- assertEquals(restoredAutoAddTiles,
- mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
- }
-
- @Test
- public void testSettingRestoredWithTilesRemovedInSource_sameAutoAddedinTarget() {
- verify(mBroadcastDispatcher)
- .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
- anyInt(), any());
-
- // These tiles were present in the original device
- String restoredTiles = "saver,internet,cast";
- Intent restoreTilesIntent =
- makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
- // And these tiles have been auto-added in the original device
- // (no auto-added before restore)
- String restoredAutoAddTiles = "work";
- Intent restoreAutoAddTilesIntent =
- makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "work", restoredAutoAddTiles);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
-
- // Then, remove work tile
- verify(mQSHost).removeTiles(List.of("work"));
- assertEquals(restoredAutoAddTiles,
- mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
- }
-
- @Test
- public void testSettingRestoredWithTilesRemovedInSource_othersAutoAddedinTarget() {
- verify(mBroadcastDispatcher)
- .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
- anyInt(), any());
-
- // These tiles were present in the original device
- String restoredTiles = "saver,internet,cast";
- Intent restoreTilesIntent =
- makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
- // And these tiles have been auto-added in the original device
- // (no auto-added before restore)
- String restoredAutoAddTiles = "work";
- Intent restoreAutoAddTilesIntent =
- makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "inversion", restoredAutoAddTiles);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
-
- // Then, remove work tile
- verify(mQSHost).removeTiles(List.of("work"));
-
- String setting = mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER);
- assertEquals(2, setting.split(",").length);
- assertTrue(setting.contains("work"));
- assertTrue(setting.contains("inversion"));
- }
-
-
- private Intent makeRestoreIntent(
- String settingName, String previousValue, String restoredValue) {
- Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED);
- intent.putExtra(Intent.EXTRA_SETTING_NAME, settingName);
- intent.putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue);
- intent.putExtra(Intent.EXTRA_SETTING_NEW_VALUE, restoredValue);
- return intent;
- }
-
- private AutoAddTracker createAutoAddTracker(int user) {
- // Null handler wil dispatch sync.
- return new AutoAddTracker(
- mSecureSettings,
- mBroadcastDispatcher,
- mQSHost,
- mDumpManager,
- null,
- mBackgroundExecutor,
- user
- );
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
deleted file mode 100644
index 6d1bc82..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ /dev/null
@@ -1,786 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-
-import static com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE;
-import static com.android.systemui.Flags.FLAG_QS_NEW_TILES;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.util.SparseArray;
-
-import androidx.annotation.Nullable;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.util.CollectionUtils;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.Expandable;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.nano.SystemUIProtoDump;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.plugins.qs.QSFactory;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.qs.external.CustomTileStatePersister;
-import com.android.systemui.qs.external.TileLifecycleManager;
-import com.android.systemui.qs.external.TileServiceKey;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.qs.tiles.di.NewQSTileFactory;
-import com.android.systemui.res.R;
-import com.android.systemui.settings.UserFileManager;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.FakeSharedPreferences;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.FakeSettings;
-import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import dagger.Lazy;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-import javax.inject.Provider;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class QSTileHostTest extends SysuiTestCase {
-
- private static String MOCK_STATE_STRING = "MockState";
- private static ComponentName CUSTOM_TILE =
- ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS");
- private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE);
- private static final String SETTING = QSHost.TILES_SETTING;
- @Mock
- private PluginManager mPluginManager;
- @Mock
- private TunerService mTunerService;
- @Mock
- private AutoTileManager mAutoTiles;
- @Mock
- private ShadeController mShadeController;
- @Mock
- private QSLogger mQSLogger;
- @Mock
- private CustomTile mCustomTile;
- @Mock
- private UserTracker mUserTracker;
- @Mock
- private CustomTileStatePersister mCustomTileStatePersister;
- @Mock
- private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
- @Mock
- private TileLifecycleManager mTileLifecycleManager;
- @Mock
- private UserFileManager mUserFileManager;
-
- private SecureSettings mSecureSettings;
-
- private QSFactory mDefaultFactory;
-
- private SparseArray<SharedPreferences> mSharedPreferencesByUser;
-
- private QSPipelineFlagsRepository mQSPipelineFlagsRepository;
-
- private FakeExecutor mMainExecutor;
-
- private QSTileHost mQSTileHost;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
-
- mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE);
- mSetFlagsRule.disableFlags(FLAG_QS_NEW_TILES);
- mQSPipelineFlagsRepository = new QSPipelineFlagsRepository();
-
- mMainExecutor = new FakeExecutor(new FakeSystemClock());
-
- mSharedPreferencesByUser = new SparseArray<>();
- when(mTileLifecycleManagerFactory
- .create(any(Intent.class), any(UserHandle.class)))
- .thenReturn(mTileLifecycleManager);
- when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
- .thenAnswer((Answer<SharedPreferences>) invocation -> {
- assertEquals(QSTileHost.TILES, invocation.getArgument(0));
- int userId = invocation.getArgument(2);
- if (!mSharedPreferencesByUser.contains(userId)) {
- mSharedPreferencesByUser.put(userId, new FakeSharedPreferences());
- }
- return mSharedPreferencesByUser.get(userId);
- });
-
- mSecureSettings = new FakeSettings();
- saveSetting("");
- setUpTileFactory();
- mQSTileHost = new TestQSTileHost(mContext, () -> null, mDefaultFactory, mMainExecutor,
- mPluginManager, mTunerService, () -> mAutoTiles, () -> mShadeController,
- mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
- mTileLifecycleManagerFactory, mUserFileManager, mQSPipelineFlagsRepository);
- mMainExecutor.runAllReady();
-
- mSecureSettings.registerContentObserverForUserSync(SETTING, new ContentObserver(null) {
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- mMainExecutor.execute(() -> mQSTileHost.onTuningChanged(SETTING, getSetting()));
- mMainExecutor.runAllReady();
- }
- }, mUserTracker.getUserId());
- }
-
- private void saveSetting(String value) {
- mSecureSettings.putStringForUser(
- SETTING, value, "", false, mUserTracker.getUserId(), false);
- }
-
- private String getSetting() {
- return mSecureSettings.getStringForUser(SETTING, mUserTracker.getUserId());
- }
-
- private void setUpTileFactory() {
- mDefaultFactory = new FakeQSFactory(spec -> {
- if ("spec1".equals(spec)) {
- return new TestTile1(mQSTileHost);
- } else if ("spec2".equals(spec)) {
- return new TestTile2(mQSTileHost);
- } else if ("spec3".equals(spec)) {
- return new TestTile3(mQSTileHost);
- } else if ("na".equals(spec)) {
- return new NotAvailableTile(mQSTileHost);
- } else if (CUSTOM_TILE_SPEC.equals(spec)) {
- QSTile tile = mCustomTile;
- QSTile.State s = mock(QSTile.State.class);
- s.spec = spec;
- when(mCustomTile.getState()).thenReturn(s);
- return tile;
- } else if ("internet".equals(spec)
- || "wifi".equals(spec)
- || "cell".equals(spec)) {
- return new TestTile1(mQSTileHost);
- } else {
- return null;
- }
- });
- when(mCustomTile.isAvailable()).thenReturn(true);
- }
-
- @Test
- public void testLoadTileSpecs_emptySetting() {
- List<String> tiles = QSTileHost.loadTileSpecs(mContext, "");
- assertFalse(tiles.isEmpty());
- }
-
- @Test
- public void testLoadTileSpecs_nullSetting() {
- List<String> tiles = QSTileHost.loadTileSpecs(mContext, null);
- assertFalse(tiles.isEmpty());
- }
-
- @Test
- public void testInvalidSpecUsesDefault() {
- mContext.getOrCreateTestableResources()
- .addOverride(R.string.quick_settings_tiles, "spec1,spec2");
- saveSetting("not-valid");
-
- assertEquals(2, mQSTileHost.getTiles().size());
- }
-
- @Test
- public void testRemoveWifiAndCellularWithoutInternet() {
- saveSetting("wifi, spec1, cell, spec2");
-
- assertEquals("internet", mQSTileHost.getSpecs().get(0));
- assertEquals("spec1", mQSTileHost.getSpecs().get(1));
- assertEquals("spec2", mQSTileHost.getSpecs().get(2));
- }
-
- @Test
- public void testRemoveWifiAndCellularWithInternet() {
- saveSetting("wifi, spec1, cell, spec2, internet");
-
- assertEquals("spec1", mQSTileHost.getSpecs().get(0));
- assertEquals("spec2", mQSTileHost.getSpecs().get(1));
- assertEquals("internet", mQSTileHost.getSpecs().get(2));
- }
-
- @Test
- public void testRemoveWifiWithoutInternet() {
- saveSetting("spec1, wifi, spec2");
-
- assertEquals("spec1", mQSTileHost.getSpecs().get(0));
- assertEquals("internet", mQSTileHost.getSpecs().get(1));
- assertEquals("spec2", mQSTileHost.getSpecs().get(2));
- }
-
- @Test
- public void testRemoveCellWithInternet() {
- saveSetting("spec1, spec2, cell, internet");
-
- assertEquals("spec1", mQSTileHost.getSpecs().get(0));
- assertEquals("spec2", mQSTileHost.getSpecs().get(1));
- assertEquals("internet", mQSTileHost.getSpecs().get(2));
- }
-
- @Test
- public void testNoWifiNoCellularNoInternet() {
- saveSetting("spec1,spec2");
-
- assertEquals("spec1", mQSTileHost.getSpecs().get(0));
- assertEquals("spec2", mQSTileHost.getSpecs().get(1));
- }
-
- @Test
- public void testSpecWithInvalidDoesNotUseDefault() {
- mContext.getOrCreateTestableResources()
- .addOverride(R.string.quick_settings_tiles, "spec1,spec2");
- saveSetting("spec2,not-valid");
-
- assertEquals(1, mQSTileHost.getTiles().size());
- QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles());
- assertTrue(element instanceof TestTile2);
- }
-
- @Test
- public void testDump() {
- saveSetting("spec1,spec2");
- StringWriter w = new StringWriter();
- PrintWriter pw = new PrintWriter(w);
- mQSTileHost.dump(pw, new String[]{});
-
- String output = "QSTileHost:" + "\n"
- + "tile specs: [spec1, spec2]" + "\n"
- + "current user: 0" + "\n"
- + "is dirty: false" + "\n"
- + "tiles:" + "\n"
- + "TestTile1:" + "\n"
- + " MockState" + "\n"
- + "TestTile2:" + "\n"
- + " MockState" + "\n";
-
- System.out.println(output);
- System.out.println(w.getBuffer().toString());
-
- assertEquals(output, w.getBuffer().toString());
- }
-
- @Test
- public void testDefault() {
- mContext.getOrCreateTestableResources()
- .addOverride(R.string.quick_settings_tiles_default, "spec1");
- saveSetting("default");
- assertEquals(1, mQSTileHost.getTiles().size());
- QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles());
- assertTrue(element instanceof TestTile1);
- verify(mQSLogger).logTileAdded("spec1");
- }
-
- @Test
- public void testNoRepeatedSpecs_addTile() {
- mContext.getOrCreateTestableResources()
- .addOverride(R.string.quick_settings_tiles, "spec1,spec2");
- saveSetting("spec1,spec2");
-
- mQSTileHost.addTile("spec1");
-
- assertEquals(2, mQSTileHost.getSpecs().size());
- assertEquals("spec1", mQSTileHost.getSpecs().get(0));
- assertEquals("spec2", mQSTileHost.getSpecs().get(1));
- }
-
- @Test
- public void testAddTileAtValidPosition() {
- mContext.getOrCreateTestableResources()
- .addOverride(R.string.quick_settings_tiles, "spec1,spec3");
- saveSetting("spec1,spec3");
-
- mQSTileHost.addTile("spec2", 1);
- mMainExecutor.runAllReady();
-
- assertEquals(3, mQSTileHost.getSpecs().size());
- assertEquals("spec1", mQSTileHost.getSpecs().get(0));
- assertEquals("spec2", mQSTileHost.getSpecs().get(1));
- assertEquals("spec3", mQSTileHost.getSpecs().get(2));
- }
-
- @Test
- public void testAddTileAtInvalidPositionAddsToEnd() {
- mContext.getOrCreateTestableResources()
- .addOverride(R.string.quick_settings_tiles, "spec1,spec3");
- saveSetting("spec1,spec3");
-
- mQSTileHost.addTile("spec2", 100);
- mMainExecutor.runAllReady();
-
- assertEquals(3, mQSTileHost.getSpecs().size());
- assertEquals("spec1", mQSTileHost.getSpecs().get(0));
- assertEquals("spec3", mQSTileHost.getSpecs().get(1));
- assertEquals("spec2", mQSTileHost.getSpecs().get(2));
- }
-
- @Test
- public void testAddTileAtEnd() {
- mContext.getOrCreateTestableResources()
- .addOverride(R.string.quick_settings_tiles, "spec1,spec3");
- saveSetting("spec1,spec3");
-
- mQSTileHost.addTile("spec2", QSTileHost.POSITION_AT_END);
- mMainExecutor.runAllReady();
-
- assertEquals(3, mQSTileHost.getSpecs().size());
- assertEquals("spec1", mQSTileHost.getSpecs().get(0));
- assertEquals("spec3", mQSTileHost.getSpecs().get(1));
- assertEquals("spec2", mQSTileHost.getSpecs().get(2));
- }
-
- @Test
- public void testNoRepeatedSpecs_customTile() {
- saveSetting(CUSTOM_TILE_SPEC);
-
- mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
- mMainExecutor.runAllReady();
-
- assertEquals(1, mQSTileHost.getSpecs().size());
- assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
- }
-
- @Test
- public void testAddedAtBeginningOnDefault_customTile() {
- saveSetting("spec1"); // seed
-
- mQSTileHost.addTile(CUSTOM_TILE);
- mMainExecutor.runAllReady();
-
- assertEquals(2, mQSTileHost.getSpecs().size());
- assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
- }
-
- @Test
- public void testAddedAtBeginning_customTile() {
- saveSetting("spec1"); // seed
-
- mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
- mMainExecutor.runAllReady();
-
- assertEquals(2, mQSTileHost.getSpecs().size());
- assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
- }
-
- @Test
- public void testAddedAtEnd_customTile() {
- saveSetting("spec1"); // seed
-
- mQSTileHost.addTile(CUSTOM_TILE, /* end */ true);
- mMainExecutor.runAllReady();
-
- assertEquals(2, mQSTileHost.getSpecs().size());
- assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(1));
- }
-
- @Test
- public void testLoadTileSpec_repeated() {
- List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2");
-
- assertEquals(2, specs.size());
- assertEquals("spec1", specs.get(0));
- assertEquals("spec2", specs.get(1));
- }
-
- @Test
- public void testLoadTileSpec_repeatedInDefault() {
- mContext.getOrCreateTestableResources()
- .addOverride(R.string.quick_settings_tiles_default, "spec1,spec1");
- List<String> specs = QSTileHost.loadTileSpecs(mContext, "default");
- }
-
- @Test
- public void testLoadTileSpec_repeatedDefaultAndSetting() {
- mContext.getOrCreateTestableResources()
- .addOverride(R.string.quick_settings_tiles_default, "spec1");
- List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1");
- }
-
- @Test
- public void testNotAvailableTile_specNotNull() {
- saveSetting("na");
- verify(mQSLogger, never()).logTileDestroyed(isNull(), anyString());
- }
-
- @Test
- public void testCustomTileRemoved_stateDeleted() {
- mQSTileHost.changeTilesByUser(List.of(CUSTOM_TILE_SPEC), List.of());
-
- verify(mCustomTileStatePersister)
- .removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId()));
- }
-
- @Test
- public void testRemoveTiles() {
- saveSetting("spec1,spec2,spec3");
-
- mQSTileHost.removeTiles(List.of("spec1", "spec2"));
-
- mMainExecutor.runAllReady();
- assertEquals(List.of("spec3"), mQSTileHost.getSpecs());
- }
-
- @Test
- public void testTilesRemovedInQuickSuccession() {
- saveSetting("spec1,spec2,spec3");
- mQSTileHost.removeTile("spec1");
- mQSTileHost.removeTile("spec3");
-
- mMainExecutor.runAllReady();
- assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
- assertEquals("spec2", getSetting());
- }
-
- @Test
- public void testAddTileInMainThread() {
- saveSetting("spec1,spec2");
-
- mQSTileHost.addTile("spec3");
- assertEquals(List.of("spec1", "spec2"), mQSTileHost.getSpecs());
-
- mMainExecutor.runAllReady();
- assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.getSpecs());
- }
-
- @Test
- public void testRemoveTileInMainThread() {
- saveSetting("spec1,spec2");
-
- mQSTileHost.removeTile("spec1");
- assertEquals(List.of("spec1", "spec2"), mQSTileHost.getSpecs());
-
- mMainExecutor.runAllReady();
- assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
- }
-
- @Test
- public void testRemoveTilesInMainThread() {
- saveSetting("spec1,spec2,spec3");
-
- mQSTileHost.removeTiles(List.of("spec3", "spec1"));
- assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.getSpecs());
-
- mMainExecutor.runAllReady();
- assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
- }
-
- @Test
- public void testRemoveTileByUserInMainThread() {
- saveSetting("spec1," + CUSTOM_TILE_SPEC);
-
- mQSTileHost.removeTileByUser(CUSTOM_TILE);
- assertEquals(List.of("spec1", CUSTOM_TILE_SPEC), mQSTileHost.getSpecs());
-
- mMainExecutor.runAllReady();
- assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
- }
-
- @Test
- public void testNonValidTileNotStoredInSettings() {
- saveSetting("spec1,not-valid");
-
- assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
- assertEquals("spec1", getSetting());
- }
-
- @Test
- public void testNotAvailableTileNotStoredInSettings() {
- saveSetting("spec1,na");
-
- assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
- assertEquals("spec1", getSetting());
- }
-
- @Test
- public void testIsTileAdded_true() {
- int user = mUserTracker.getUserId();
- getSharedPreferencesForUser(user)
- .edit()
- .putBoolean(CUSTOM_TILE.flattenToString(), true)
- .apply();
-
- assertTrue(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
- }
-
- @Test
- public void testIsTileAdded_false() {
- int user = mUserTracker.getUserId();
- getSharedPreferencesForUser(user)
- .edit()
- .putBoolean(CUSTOM_TILE.flattenToString(), false)
- .apply();
-
- assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
- }
-
- @Test
- public void testIsTileAdded_notSet() {
- int user = mUserTracker.getUserId();
-
- assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
- }
-
- @Test
- public void testIsTileAdded_differentUser() {
- int user = mUserTracker.getUserId();
- mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user)
- .edit()
- .putBoolean(CUSTOM_TILE.flattenToString(), true)
- .apply();
-
- assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user + 1));
- }
-
- @Test
- public void testSetTileAdded_true() {
- int user = mUserTracker.getUserId();
- mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
- assertTrue(getSharedPreferencesForUser(user)
- .getBoolean(CUSTOM_TILE.flattenToString(), false));
- }
-
- @Test
- public void testSetTileAdded_false() {
- int user = mUserTracker.getUserId();
- mQSTileHost.setTileAdded(CUSTOM_TILE, user, false);
-
- assertFalse(getSharedPreferencesForUser(user)
- .getBoolean(CUSTOM_TILE.flattenToString(), false));
- }
-
- @Test
- public void testSetTileAdded_differentUser() {
- int user = mUserTracker.getUserId();
- mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
- assertFalse(getSharedPreferencesForUser(user + 1)
- .getBoolean(CUSTOM_TILE.flattenToString(), false));
- }
-
- @Test
- public void testSetTileRemoved_afterCustomTileChangedByUser() {
- int user = mUserTracker.getUserId();
- saveSetting(CUSTOM_TILE_SPEC);
-
- // This will be done by TileServiceManager
- mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
- mQSTileHost.changeTilesByUser(mQSTileHost.getSpecs(), List.of("spec1"));
- assertFalse(getSharedPreferencesForUser(user)
- .getBoolean(CUSTOM_TILE.flattenToString(), false));
- }
-
- @Test
- public void testSetTileRemoved_removedByUser() {
- int user = mUserTracker.getUserId();
- saveSetting(CUSTOM_TILE_SPEC);
-
- // This will be done by TileServiceManager
- mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
- mQSTileHost.removeTileByUser(CUSTOM_TILE);
- mMainExecutor.runAllReady();
- assertFalse(getSharedPreferencesForUser(user)
- .getBoolean(CUSTOM_TILE.flattenToString(), false));
- }
-
- @Test
- public void testSetTileRemoved_removedBySystem() {
- int user = mUserTracker.getUserId();
- saveSetting("spec1," + CUSTOM_TILE_SPEC);
-
- // This will be done by TileServiceManager
- mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
- mQSTileHost.removeTile(CUSTOM_TILE_SPEC);
- mMainExecutor.runAllReady();
- assertFalse(getSharedPreferencesForUser(user)
- .getBoolean(CUSTOM_TILE.flattenToString(), false));
- }
-
- @Test
- public void testProtoDump_noTiles() {
- SystemUIProtoDump proto = new SystemUIProtoDump();
- mQSTileHost.dumpProto(proto, new String[0]);
-
- assertEquals(0, proto.tiles.length);
- }
-
- @Test
- public void testTilesInOrder() {
- saveSetting("spec1," + CUSTOM_TILE_SPEC);
-
- SystemUIProtoDump proto = new SystemUIProtoDump();
- mQSTileHost.dumpProto(proto, new String[0]);
-
- assertEquals(2, proto.tiles.length);
- assertEquals("spec1", proto.tiles[0].getSpec());
- assertEquals(CUSTOM_TILE.getPackageName(), proto.tiles[1].getComponentName().packageName);
- assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className);
- }
-
- private SharedPreferences getSharedPreferencesForUser(int user) {
- return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
- }
-
- private class TestQSTileHost extends QSTileHost {
- TestQSTileHost(Context context, Lazy<NewQSTileFactory> newQSTileFactoryProvider,
- QSFactory defaultFactory, Executor mainExecutor,
- PluginManager pluginManager, TunerService tunerService,
- Provider<AutoTileManager> autoTiles,
- Lazy<ShadeController> shadeController, QSLogger qsLogger,
- UserTracker userTracker, SecureSettings secureSettings,
- CustomTileStatePersister customTileStatePersister,
- TileLifecycleManager.Factory tileLifecycleManagerFactory,
- UserFileManager userFileManager, QSPipelineFlagsRepository featureFlags) {
- super(context, newQSTileFactoryProvider, defaultFactory, mainExecutor, pluginManager,
- tunerService, autoTiles, shadeController, qsLogger,
- userTracker, secureSettings, customTileStatePersister,
- tileLifecycleManagerFactory, userFileManager, featureFlags);
- }
-
- @Override
- public void onPluginConnected(QSFactory plugin, Context pluginContext) {
- }
-
- @Override
- public void onPluginDisconnected(QSFactory plugin) {
- }
- }
-
-
- private class TestTile extends QSTileImpl<QSTile.State> {
-
- protected TestTile(QSHost host) {
- super(
- host,
- mock(QsEventLogger.class),
- mock(Looper.class),
- mock(Handler.class),
- new FalsingManagerFake(),
- mock(MetricsLogger.class),
- mock(StatusBarStateController.class),
- mock(ActivityStarter.class),
- QSTileHostTest.this.mQSLogger
- );
- }
-
- @Override
- public State newTileState() {
- State s = mock(QSTile.State.class);
- when(s.toString()).thenReturn(MOCK_STATE_STRING);
- return s;
- }
-
- @Override
- protected void handleClick(@Nullable Expandable expandable) {}
-
- @Override
- protected void handleUpdateState(State state, Object arg) {}
-
- @Override
- public int getMetricsCategory() {
- return 0;
- }
-
- @Override
- public Intent getLongClickIntent() {
- return null;
- }
-
- @Override
- public CharSequence getTileLabel() {
- return null;
- }
- }
-
- private class TestTile1 extends TestTile {
-
- protected TestTile1(QSHost host) {
- super(host);
- }
- }
-
- private class TestTile2 extends TestTile {
-
- protected TestTile2(QSHost host) {
- super(host);
- }
- }
-
- private class TestTile3 extends TestTile {
-
- protected TestTile3(QSHost host) {
- super(host);
- }
- }
-
- private class NotAvailableTile extends TestTile {
-
- protected NotAvailableTile(QSHost host) {
- super(host);
- }
-
- @Override
- public boolean isAvailable() {
- return false;
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
index 970cd17..090a85b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
@@ -15,20 +15,6 @@
private val underTest = QSPipelineFlagsRepository()
@Test
- fun pipelineFlagDisabled() {
- mSetFlagsRule.disableFlags(Flags.FLAG_QS_NEW_PIPELINE)
-
- assertThat(underTest.pipelineEnabled).isFalse()
- }
-
- @Test
- fun pipelineFlagEnabled() {
- mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE)
-
- assertThat(underTest.pipelineEnabled).isTrue()
- }
-
- @Test
fun tilesFlagDisabled() {
mSetFlagsRule.disableFlags(Flags.FLAG_QS_NEW_TILES)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
deleted file mode 100644
index 665544d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ /dev/null
@@ -1,648 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE;
-import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
-import static com.android.systemui.statusbar.phone.AutoTileManager.DEVICE_CONTROLS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNotNull;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.hardware.display.ColorDisplayManager;
-import android.hardware.display.NightDisplayListener;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dagger.NightDisplayListenerModule;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.AutoAddTracker;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.UserSettingObserver;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.CastDevice;
-import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.DeviceControlsController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.SafetyController;
-import com.android.systemui.statusbar.policy.WalletController;
-import com.android.systemui.util.settings.FakeSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-import org.mockito.stubbing.Answer;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import javax.inject.Named;
-
-@RunWith(AndroidJUnit4.class)
-@RunWithLooper
-@SmallTest
-public class AutoTileManagerTest extends SysuiTestCase {
-
- private static final String TEST_SETTING = "setting";
- private static final String TEST_SPEC = "spec";
- private static final String TEST_SETTING_COMPONENT = "setting_component";
- private static final String TEST_COMPONENT = "test_pkg/test_cls";
- private static final String TEST_CUSTOM_SPEC = "custom(" + TEST_COMPONENT + ")";
- private static final String TEST_CUSTOM_SAFETY_CLASS = "safety_cls";
- private static final String TEST_CUSTOM_SAFETY_PKG = "safety_pkg";
- private static final String TEST_CUSTOM_SAFETY_SPEC = CustomTile.toSpec(new ComponentName(
- TEST_CUSTOM_SAFETY_PKG, TEST_CUSTOM_SAFETY_CLASS));
- private static final String SEPARATOR = AutoTileManager.SETTING_SEPARATOR;
-
- private static final int USER = 0;
-
- @Mock private QSHost mQsHost;
- @Mock private AutoAddTracker mAutoAddTracker;
- @Mock private CastController mCastController;
- @Mock private HotspotController mHotspotController;
- @Mock private DataSaverController mDataSaverController;
- @Mock private ManagedProfileController mManagedProfileController;
- @Mock private NightDisplayListener mNightDisplayListener;
- @Mock(answer = Answers.RETURNS_SELF)
- private NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
- @Mock private ReduceBrightColorsController mReduceBrightColorsController;
- @Mock private DeviceControlsController mDeviceControlsController;
- @Mock private WalletController mWalletController;
- @Mock private SafetyController mSafetyController;
- @Mock(answer = Answers.RETURNS_SELF)
- private AutoAddTracker.Builder mAutoAddTrackerBuilder;
- @Mock private Context mUserContext;
- @Spy private PackageManager mPackageManager;
- private final boolean mIsReduceBrightColorsAvailable = true;
-
- private AutoTileManager mAutoTileManager; // under test
-
- private SecureSettings mSecureSettings;
- private ManagedProfileController.Callback mManagedProfileCallback;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mSecureSettings = new FakeSettings();
-
- mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE);
-
- mContext.getOrCreateTestableResources().addOverride(
- R.array.config_quickSettingsAutoAdd,
- new String[] {
- TEST_SETTING + SEPARATOR + TEST_SPEC,
- TEST_SETTING_COMPONENT + SEPARATOR + TEST_CUSTOM_SPEC
- }
- );
- mContext.getOrCreateTestableResources().addOverride(
- com.android.internal.R.bool.config_nightDisplayAvailable, true);
- mContext.getOrCreateTestableResources().addOverride(
- R.string.safety_quick_settings_tile_class, TEST_CUSTOM_SAFETY_CLASS);
-
- when(mAutoAddTrackerBuilder.build()).thenReturn(mAutoAddTracker);
- when(mQsHost.getUserContext()).thenReturn(mUserContext);
- when(mUserContext.getUser()).thenReturn(UserHandle.of(USER));
- mPackageManager = Mockito.spy(mContext.getPackageManager());
- when(mPackageManager.getPermissionControllerPackageName())
- .thenReturn(TEST_CUSTOM_SAFETY_PKG);
- Context context = Mockito.spy(mContext);
- when(context.getPackageManager()).thenReturn(mPackageManager);
- when(mNightDisplayListenerBuilder.build()).thenReturn(mNightDisplayListener);
-
- mAutoTileManager = createAutoTileManager(context);
- mAutoTileManager.init();
- }
-
- @After
- public void tearDown() {
- mAutoTileManager.destroy();
- }
-
- private AutoTileManager createAutoTileManager(
- Context context,
- AutoAddTracker.Builder autoAddTrackerBuilder,
- HotspotController hotspotController,
- DataSaverController dataSaverController,
- ManagedProfileController managedProfileController,
- NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
- CastController castController,
- ReduceBrightColorsController reduceBrightColorsController,
- DeviceControlsController deviceControlsController,
- WalletController walletController,
- SafetyController safetyController,
- @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
- return new AutoTileManager(context, autoAddTrackerBuilder, mQsHost,
- Handler.createAsync(TestableLooper.get(this).getLooper()),
- mSecureSettings,
- hotspotController,
- dataSaverController,
- managedProfileController,
- mNightDisplayListenerBuilder,
- castController,
- reduceBrightColorsController,
- deviceControlsController,
- walletController,
- safetyController,
- isReduceBrightColorsAvailable);
- }
-
- private AutoTileManager createAutoTileManager(Context context) {
- return createAutoTileManager(context, mAutoAddTrackerBuilder, mHotspotController,
- mDataSaverController, mManagedProfileController, mNightDisplayListenerBuilder,
- mCastController, mReduceBrightColorsController, mDeviceControlsController,
- mWalletController, mSafetyController, mIsReduceBrightColorsAvailable);
- }
-
- @Test
- public void testCreatedAutoTileManagerIsNotInitialized() {
- AutoAddTracker.Builder builder = mock(AutoAddTracker.Builder.class, Answers.RETURNS_SELF);
- AutoAddTracker tracker = mock(AutoAddTracker.class);
- when(builder.build()).thenReturn(tracker);
- HotspotController hC = mock(HotspotController.class);
- DataSaverController dSC = mock(DataSaverController.class);
- ManagedProfileController mPC = mock(ManagedProfileController.class);
- NightDisplayListenerModule.Builder nDSB = mock(NightDisplayListenerModule.Builder.class);
- CastController cC = mock(CastController.class);
- ReduceBrightColorsController rBC = mock(ReduceBrightColorsController.class);
- DeviceControlsController dCC = mock(DeviceControlsController.class);
- WalletController wC = mock(WalletController.class);
- SafetyController sC = mock(SafetyController.class);
-
- AutoTileManager manager =
- createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDSB, cC, rBC,
- dCC, wC, sC, true);
-
- verify(tracker, never()).initialize();
- verify(hC, never()).addCallback(any());
- verify(dSC, never()).addCallback(any());
- verify(mPC, never()).addCallback(any());
- verifyNoMoreInteractions(nDSB);
- verify(cC, never()).addCallback(any());
- verify(rBC, never()).addCallback(any());
- verify(dCC, never()).setCallback(any());
- verify(wC, never()).getWalletPosition();
- verify(sC, never()).addCallback(any());
- assertNull(manager.getSecureSettingForKey(TEST_SETTING));
- assertNull(manager.getSecureSettingForKey(TEST_SETTING_COMPONENT));
- }
-
- @Test
- public void testChangeUserWhenNotInitializedThrows() {
- AutoTileManager manager = createAutoTileManager(mock(Context.class));
-
- try {
- manager.changeUser(UserHandle.of(USER + 1));
- fail();
- } catch (Exception e) {
- // This should throw and take this path
- }
- }
-
- @Test
- public void testChangeUserCallbacksStoppedAndStarted() throws Exception {
- TestableLooper.get(this).runWithLooper(() ->
- mAutoTileManager.changeUser(UserHandle.of(USER + 1))
- );
-
- InOrder inOrderHotspot = inOrder(mHotspotController);
- inOrderHotspot.verify(mHotspotController).removeCallback(any());
- inOrderHotspot.verify(mHotspotController).addCallback(any());
-
- InOrder inOrderDataSaver = inOrder(mDataSaverController);
- inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
- inOrderDataSaver.verify(mDataSaverController).addCallback(any());
-
- InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
- inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
- inOrderManagedProfile.verify(mManagedProfileController).addCallback(any());
-
- if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
- InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
- inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
- inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
- }
-
- InOrder inOrderReduceBrightColors = inOrder(mReduceBrightColorsController);
- inOrderReduceBrightColors.verify(mReduceBrightColorsController).removeCallback(any());
- inOrderReduceBrightColors.verify(mReduceBrightColorsController).addCallback(any());
-
- InOrder inOrderCast = inOrder(mCastController);
- inOrderCast.verify(mCastController).removeCallback(any());
- inOrderCast.verify(mCastController).addCallback(any());
-
- InOrder inOrderDevices = inOrder(mDeviceControlsController);
- inOrderDevices.verify(mDeviceControlsController).removeCallback();
- inOrderDevices.verify(mDeviceControlsController).setCallback(any());
-
- verify(mWalletController, times(2)).getWalletPosition();
-
- InOrder inOrderSafety = inOrder(mSafetyController);
- inOrderSafety.verify(mSafetyController).removeCallback(any());
- inOrderSafety.verify(mSafetyController).addCallback(any());
-
- UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
- assertEquals(USER + 1, setting.getCurrentUser());
- assertTrue(setting.isListening());
- }
-
- @Test
- public void testChangeUserSomeCallbacksNotAdded() throws Exception {
- when(mAutoAddTracker.isAdded("hotspot")).thenReturn(true);
- when(mAutoAddTracker.isAdded("work")).thenReturn(true);
- when(mAutoAddTracker.isAdded("cast")).thenReturn(true);
- when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true);
-
- TestableLooper.get(this).runWithLooper(() ->
- mAutoTileManager.changeUser(UserHandle.of(USER + 1))
- );
-
- verify(mAutoAddTracker).changeUser(UserHandle.of(USER + 1));
-
- InOrder inOrderHotspot = inOrder(mHotspotController);
- inOrderHotspot.verify(mHotspotController).removeCallback(any());
- inOrderHotspot.verify(mHotspotController, never()).addCallback(any());
-
- InOrder inOrderDataSaver = inOrder(mDataSaverController);
- inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
- inOrderDataSaver.verify(mDataSaverController).addCallback(any());
-
- InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
- inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
- inOrderManagedProfile.verify(mManagedProfileController).addCallback(any());
-
- if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
- InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
- inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
- inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
- }
-
- InOrder inOrderReduceBrightColors = inOrder(mReduceBrightColorsController);
- inOrderReduceBrightColors.verify(mReduceBrightColorsController).removeCallback(any());
- inOrderReduceBrightColors.verify(mReduceBrightColorsController).addCallback(any());
-
- InOrder inOrderCast = inOrder(mCastController);
- inOrderCast.verify(mCastController).removeCallback(any());
- inOrderCast.verify(mCastController, never()).addCallback(any());
-
- InOrder inOrderDevices = inOrder(mDeviceControlsController);
- inOrderDevices.verify(mDeviceControlsController).removeCallback();
- inOrderDevices.verify(mDeviceControlsController).setCallback(any());
-
- verify(mWalletController, times(2)).getWalletPosition();
-
- InOrder inOrderSafety = inOrder(mSafetyController);
- inOrderSafety.verify(mSafetyController).removeCallback(any());
- inOrderSafety.verify(mSafetyController).addCallback(any());
-
- UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
- assertEquals(USER + 1, setting.getCurrentUser());
- assertFalse(setting.isListening());
- }
-
- @Test
- public void testGetCurrentUserId() throws Exception {
- assertEquals(USER, mAutoTileManager.getCurrentUserId());
-
- TestableLooper.get(this).runWithLooper(() ->
- mAutoTileManager.changeUser(UserHandle.of(USER + 100))
- );
-
- assertEquals(USER + 100, mAutoTileManager.getCurrentUserId());
- }
-
- @Test
- public void nightTileAdded_whenActivated() {
- if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
- return;
- }
- mAutoTileManager.mNightDisplayCallback.onActivated(true);
- verify(mQsHost).addTile("night");
- }
-
- @Test
- public void nightTileNotAdded_whenDeactivated() {
- if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
- return;
- }
- mAutoTileManager.mNightDisplayCallback.onActivated(false);
- verify(mQsHost, never()).addTile("night");
- }
-
- @Test
- public void nightTileAdded_whenNightModeTwilight() {
- if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
- return;
- }
- mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
- ColorDisplayManager.AUTO_MODE_TWILIGHT);
- verify(mQsHost).addTile("night");
- }
-
- @Test
- public void nightTileAdded_whenNightModeCustom() {
- if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
- return;
- }
- mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
- ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
- verify(mQsHost).addTile("night");
- }
-
- @Test
- public void nightTileNotAdded_whenNightModeDisabled() {
- if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
- return;
- }
- mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
- ColorDisplayManager.AUTO_MODE_DISABLED);
- verify(mQsHost, never()).addTile("night");
- }
-
- @Test
- public void reduceBrightColorsTileAdded_whenActivated() {
- mAutoTileManager.mReduceBrightColorsCallback.onActivated(true);
- verify(mQsHost).addTile("reduce_brightness");
- }
-
- @Test
- public void reduceBrightColorsTileNotAdded_whenDeactivated() {
- mAutoTileManager.mReduceBrightColorsCallback.onActivated(false);
- verify(mQsHost, never()).addTile("reduce_brightness");
- }
-
- private static List<CastDevice> buildFakeCastDevice(boolean isCasting) {
- CastDevice.CastState state = isCasting
- ? CastDevice.CastState.Connected
- : CastDevice.CastState.Disconnected;
- return Collections.singletonList(
- new CastDevice(
- "id",
- /* name= */ null,
- /* description= */ null,
- /* state= */ state,
- /* origin= */ CastDevice.CastOrigin.MediaProjection,
- /* tag= */ null));
- }
-
- @Test
- public void castTileAdded_whenDeviceIsCasting() {
- doReturn(buildFakeCastDevice(true)).when(mCastController).getCastDevices();
- mAutoTileManager.mCastCallback.onCastDevicesChanged();
- verify(mQsHost).addTile("cast");
- }
-
- @Test
- public void castTileNotAdded_whenDeviceIsNotCasting() {
- doReturn(buildFakeCastDevice(false)).when(mCastController).getCastDevices();
- mAutoTileManager.mCastCallback.onCastDevicesChanged();
- verify(mQsHost, never()).addTile("cast");
- }
-
- @Test
- public void testSettingTileAdded_onChanged() {
- changeValue(TEST_SETTING, 1);
- verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
- verify(mQsHost).addTile(TEST_SPEC);
- }
-
- @Test
- public void testSettingTileAddedComponentAtEnd_onChanged() {
- changeValue(TEST_SETTING_COMPONENT, 1);
- verify(mAutoAddTracker).setTileAdded(TEST_CUSTOM_SPEC);
- verify(mQsHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT)
- , /* end */ true);
- }
-
- @Test
- public void testSettingTileAdded_onlyOnce() {
- changeValue(TEST_SETTING, 1);
- changeValue(TEST_SETTING, 2);
- verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
- verify(mQsHost).addTile(TEST_SPEC);
- }
-
- @Test
- public void testSettingTileNotAdded_onChangedTo0() {
- changeValue(TEST_SETTING, 0);
- verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
- verify(mQsHost, never()).addTile(TEST_SPEC);
- }
-
- @Test
- public void testSettingTileNotAdded_ifPreviouslyAdded() {
- when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true);
-
- changeValue(TEST_SETTING, 1);
- verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
- verify(mQsHost, never()).addTile(TEST_SPEC);
- }
-
- @Test
- public void testSafetyTileNotAdded_ifPreviouslyAdded() {
- ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
- mAutoTileManager.init();
- verify(mQsHost, times(1)).addTile(safetyComponent, true);
- when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
- mAutoTileManager.init();
- verify(mQsHost, times(1)).addTile(safetyComponent, true);
- }
-
- @Test
- public void testSafetyTileAdded_onUserChange() {
- ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
- mAutoTileManager.init();
- verify(mQsHost, times(1)).addTile(safetyComponent, true);
- when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(false);
- mAutoTileManager.changeUser(UserHandle.of(USER + 1));
- verify(mQsHost, times(2)).addTile(safetyComponent, true);
- }
-
- @Test
- public void testSafetyTileRemoved_onSafetyCenterDisable() {
- ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
- mAutoTileManager.init();
- when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
- mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
- verify(mQsHost, times(1)).removeTile(TEST_CUSTOM_SAFETY_SPEC);
- }
-
- @Test
- public void testSafetyTileAdded_onSafetyCenterEnable() {
- ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
- mAutoTileManager.init();
- verify(mQsHost, times(1)).addTile(safetyComponent, true);
- mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
- mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(true);
- verify(mQsHost, times(2)).addTile(safetyComponent, true);
- }
-
- @Test
- public void managedProfileAdded_tileAdded() {
- when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(false);
- when(mAutoAddTracker.getRestoredTilePosition(eq("work"))).thenReturn(2);
- mAutoTileManager = createAutoTileManager(mContext);
- Mockito.doAnswer((Answer<Object>) invocation -> {
- mManagedProfileCallback = invocation.getArgument(0);
- return null;
- }).when(mManagedProfileController).addCallback(any());
- mAutoTileManager.init();
- when(mManagedProfileController.hasActiveProfile()).thenReturn(true);
-
- mManagedProfileCallback.onManagedProfileChanged();
-
- verify(mQsHost, times(1)).addTile(eq("work"), eq(2));
- verify(mAutoAddTracker, times(1)).setTileAdded(eq("work"));
- }
-
- @Test
- public void managedProfileRemoved_tileRemoved() {
- when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(true);
- mAutoTileManager = createAutoTileManager(mContext);
- Mockito.doAnswer((Answer<Object>) invocation -> {
- mManagedProfileCallback = invocation.getArgument(0);
- return null;
- }).when(mManagedProfileController).addCallback(any());
- mAutoTileManager.init();
- when(mManagedProfileController.hasActiveProfile()).thenReturn(false);
-
- mManagedProfileCallback.onManagedProfileChanged();
-
- verify(mQsHost, times(1)).removeTile(eq("work"));
- verify(mAutoAddTracker, times(1)).setTileRemoved(eq("work"));
- }
-
- @Test
- public void testAddControlsTileIfNotPresent() {
- String spec = DEVICE_CONTROLS;
- when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
- when(mQsHost.getTiles()).thenReturn(new ArrayList<>());
-
- mAutoTileManager.init();
- ArgumentCaptor<DeviceControlsController.Callback> captor =
- ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
-
- verify(mDeviceControlsController).setCallback(captor.capture());
-
- captor.getValue().onControlsUpdate(3);
- verify(mQsHost).addTile(spec, 3);
- verify(mAutoAddTracker).setTileAdded(spec);
- }
-
- @Test
- public void testDontAddControlsTileIfPresent() {
- String spec = DEVICE_CONTROLS;
- when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
- when(mQsHost.getTiles()).thenReturn(new ArrayList<>());
-
- mAutoTileManager.init();
- ArgumentCaptor<DeviceControlsController.Callback> captor =
- ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
-
- verify(mDeviceControlsController).setCallback(captor.capture());
-
- captor.getValue().removeControlsAutoTracker();
- verify(mQsHost, never()).addTile(spec, 3);
- verify(mAutoAddTracker, never()).setTileAdded(spec);
- verify(mAutoAddTracker).setTileRemoved(spec);
- }
-
- @Test
- public void testRemoveControlsTileFromTrackerWhenRequested() {
- String spec = "controls";
- when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(true);
- QSTile mockTile = mock(QSTile.class);
- when(mockTile.getTileSpec()).thenReturn(spec);
- when(mQsHost.getTiles()).thenReturn(List.of(mockTile));
-
- mAutoTileManager.init();
- ArgumentCaptor<DeviceControlsController.Callback> captor =
- ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
-
- verify(mDeviceControlsController).setCallback(captor.capture());
-
- captor.getValue().onControlsUpdate(3);
- verify(mQsHost, never()).addTile(spec, 3);
- verify(mAutoAddTracker, never()).setTileAdded(spec);
- }
-
-
- @Test
- public void testEmptyArray_doesNotCrash() {
- mContext.getOrCreateTestableResources().addOverride(
- R.array.config_quickSettingsAutoAdd, new String[0]);
- createAutoTileManager(mContext).destroy();
- }
-
- @Test
- public void testMissingConfig_doesNotCrash() {
- mContext.getOrCreateTestableResources().addOverride(
- R.array.config_quickSettingsAutoAdd, null);
- createAutoTileManager(mContext).destroy();
- }
-
- @Test
- public void testUserChange_newNightDisplayListenerCreated() {
- UserHandle newUser = UserHandle.of(1000);
- mAutoTileManager.changeUser(newUser);
- InOrder inOrder = inOrder(mNightDisplayListenerBuilder);
- inOrder.verify(mNightDisplayListenerBuilder).setUser(newUser.getIdentifier());
- inOrder.verify(mNightDisplayListenerBuilder).build();
- }
-
- // Will only notify if it's listening
- private void changeValue(String key, int value) {
- mSecureSettings.putIntForUser(key, value, USER);
- TestableLooper.get(this).processAllMessages();
- }
-}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
index e70631e..e8612d08 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
package com.android.systemui.bouncer.ui.viewmodel
import android.content.applicationContext
@@ -26,26 +28,31 @@
import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.Kosmos.Fixture
import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
import com.android.systemui.util.time.systemClock
import kotlinx.coroutines.ExperimentalCoroutinesApi
-@ExperimentalCoroutinesApi
-val Kosmos.bouncerMessageViewModel by
- Kosmos.Fixture {
- BouncerMessageViewModel(
- applicationContext = applicationContext,
- applicationScope = testScope.backgroundScope,
- bouncerInteractor = bouncerInteractor,
- simBouncerInteractor = simBouncerInteractor,
- authenticationInteractor = authenticationInteractor,
- selectedUser = userSwitcherViewModel.selectedUser,
- clock = systemClock,
- biometricMessageInteractor = biometricMessageInteractor,
- faceAuthInteractor = deviceEntryFaceAuthInteractor,
- deviceUnlockedInteractor = deviceUnlockedInteractor,
- deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor,
- flags = composeBouncerFlags,
- )
+val Kosmos.bouncerMessageViewModel by Fixture {
+ BouncerMessageViewModel(
+ applicationContext = applicationContext,
+ bouncerInteractor = bouncerInteractor,
+ simBouncerInteractor = simBouncerInteractor,
+ authenticationInteractor = authenticationInteractor,
+ userSwitcherViewModel = userSwitcherViewModel,
+ clock = systemClock,
+ biometricMessageInteractor = biometricMessageInteractor,
+ faceAuthInteractor = deviceEntryFaceAuthInteractor,
+ deviceUnlockedInteractor = deviceUnlockedInteractor,
+ deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor,
+ flags = composeBouncerFlags,
+ )
+}
+
+val Kosmos.bouncerMessageViewModelFactory by Fixture {
+ object : BouncerMessageViewModel.Factory {
+ override fun create(): BouncerMessageViewModel {
+ return bouncerMessageViewModel
+ }
}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index c3dad74..e405d17 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -21,6 +21,7 @@
import android.app.admin.devicePolicyManager
import android.content.applicationContext
import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
@@ -28,28 +29,97 @@
import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
import com.android.systemui.kosmos.Kosmos
import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
import com.android.systemui.user.domain.interactor.selectedUserInteractor
import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.StateFlow
-val Kosmos.bouncerViewModel by Fixture {
- BouncerViewModel(
- applicationContext = applicationContext,
- applicationScope = testScope.backgroundScope,
- mainDispatcher = testDispatcher,
+val Kosmos.bouncerSceneActionsViewModel by Fixture {
+ BouncerSceneActionsViewModel(
bouncerInteractor = bouncerInteractor,
- inputMethodInteractor = inputMethodInteractor,
- simBouncerInteractor = simBouncerInteractor,
- authenticationInteractor = authenticationInteractor,
- selectedUserInteractor = selectedUserInteractor,
- devicePolicyManager = devicePolicyManager,
- bouncerMessageViewModel = bouncerMessageViewModel,
- flags = composeBouncerFlags,
- selectedUser = userSwitcherViewModel.selectedUser,
- users = userSwitcherViewModel.users,
- userSwitcherMenu = userSwitcherViewModel.menu,
- actionButton = bouncerActionButtonInteractor.actionButton,
)
}
+
+val Kosmos.bouncerSceneActionsViewModelFactory by Fixture {
+ object : BouncerSceneActionsViewModel.Factory {
+ override fun create(): BouncerSceneActionsViewModel {
+ return bouncerSceneActionsViewModel
+ }
+ }
+}
+
+val Kosmos.bouncerSceneContentViewModel by Fixture {
+ BouncerSceneContentViewModel(
+ applicationContext = applicationContext,
+ bouncerInteractor = bouncerInteractor,
+ authenticationInteractor = authenticationInteractor,
+ devicePolicyManager = devicePolicyManager,
+ bouncerMessageViewModelFactory = bouncerMessageViewModelFactory,
+ flags = composeBouncerFlags,
+ userSwitcher = userSwitcherViewModel,
+ actionButtonInteractor = bouncerActionButtonInteractor,
+ pinViewModelFactory = pinBouncerViewModelFactory,
+ patternViewModelFactory = patternBouncerViewModelFactory,
+ passwordViewModelFactory = passwordBouncerViewModelFactory,
+ )
+}
+
+val Kosmos.bouncerSceneContentViewModelFactory by Fixture {
+ object : BouncerSceneContentViewModel.Factory {
+ override fun create(): BouncerSceneContentViewModel {
+ return bouncerSceneContentViewModel
+ }
+ }
+}
+
+val Kosmos.pinBouncerViewModelFactory by Fixture {
+ object : PinBouncerViewModel.Factory {
+ override fun create(
+ isInputEnabled: StateFlow<Boolean>,
+ onIntentionalUserInput: () -> Unit,
+ authenticationMethod: AuthenticationMethodModel,
+ ): PinBouncerViewModel {
+ return PinBouncerViewModel(
+ applicationContext = applicationContext,
+ interactor = bouncerInteractor,
+ simBouncerInteractor = simBouncerInteractor,
+ isInputEnabled = isInputEnabled,
+ onIntentionalUserInput = onIntentionalUserInput,
+ authenticationMethod = authenticationMethod,
+ )
+ }
+ }
+}
+
+val Kosmos.patternBouncerViewModelFactory by Fixture {
+ object : PatternBouncerViewModel.Factory {
+ override fun create(
+ isInputEnabled: StateFlow<Boolean>,
+ onIntentionalUserInput: () -> Unit,
+ ): PatternBouncerViewModel {
+ return PatternBouncerViewModel(
+ applicationContext = applicationContext,
+ interactor = bouncerInteractor,
+ isInputEnabled = isInputEnabled,
+ onIntentionalUserInput = onIntentionalUserInput,
+ )
+ }
+ }
+}
+
+val Kosmos.passwordBouncerViewModelFactory by Fixture {
+ object : PasswordBouncerViewModel.Factory {
+ override fun create(
+ isInputEnabled: StateFlow<Boolean>,
+ onIntentionalUserInput: () -> Unit,
+ ): PasswordBouncerViewModel {
+ return PasswordBouncerViewModel(
+ interactor = bouncerInteractor,
+ inputMethodInteractor = inputMethodInteractor,
+ selectedUserInteractor = selectedUserInteractor,
+ isInputEnabled = isInputEnabled,
+ onIntentionalUserInput = onIntentionalUserInput,
+ )
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 9818916..abb2132 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -489,6 +489,7 @@
} else if ((record.getFlags() & Notification.FLAG_INSISTENT) != 0) {
hasValidSound = false;
+ hasValidVibrate = false;
}
}
}
@@ -753,6 +754,13 @@
// notifying app does not have the VIBRATE permission.
final long identity = Binder.clearCallingIdentity();
try {
+ // Need to explicitly cancel a previously playing vibration
+ // Otherwise a looping vibration will not be stopped when starting a new one.
+ if (mVibrateNotificationKey != null
+ && !mVibrateNotificationKey.equals(record.getKey())) {
+ mVibrateNotificationKey = null;
+ mVibratorHelper.cancelVibration();
+ }
final float scale = getVibrationIntensity(record);
final VibrationEffect scaledEffect = Float.compare(scale, DEFAULT_VOLUME) != 0
? mVibratorHelper.scale(effect, scale) : effect;
diff --git a/services/core/java/com/android/server/tracing/TracingServiceProxy.java b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
index 68eb8eb..480db25 100644
--- a/services/core/java/com/android/server/tracing/TracingServiceProxy.java
+++ b/services/core/java/com/android/server/tracing/TracingServiceProxy.java
@@ -76,8 +76,6 @@
// Keep this in sync with the definitions in TraceService
private static final String INTENT_ACTION_NOTIFY_SESSION_STOPPED =
"com.android.traceur.NOTIFY_SESSION_STOPPED";
- private static final String INTENT_ACTION_NOTIFY_SESSION_STOLEN =
- "com.android.traceur.NOTIFY_SESSION_STOLEN";
private static final int REPORT_BEGIN =
TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BEGIN;
@@ -97,13 +95,12 @@
private final ITracingServiceProxy.Stub mTracingServiceProxy = new ITracingServiceProxy.Stub() {
/**
- * Notifies system tracing app that a tracing session has ended. If a session is repurposed
- * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
- * there is no buffer available to dump.
+ * Notifies system tracing app that a tracing session has ended. sessionStolen is ignored,
+ * as trace sessions are no longer stolen and are always cloned instead.
*/
@Override
- public void notifyTraceSessionEnded(boolean sessionStolen) {
- TracingServiceProxy.this.notifyTraceur(sessionStolen);
+ public void notifyTraceSessionEnded(boolean sessionStolen /* unused */) {
+ TracingServiceProxy.this.notifyTraceur();
}
@Override
@@ -132,7 +129,7 @@
}
}
- private void notifyTraceur(boolean sessionStolen) {
+ private void notifyTraceur() {
final Intent intent = new Intent();
try {
@@ -141,11 +138,7 @@
PackageManager.MATCH_SYSTEM_ONLY);
intent.setClassName(info.packageName, TRACING_APP_ACTIVITY);
- if (sessionStolen) {
- intent.setAction(INTENT_ACTION_NOTIFY_SESSION_STOLEN);
- } else {
- intent.setAction(INTENT_ACTION_NOTIFY_SESSION_STOPPED);
- }
+ intent.setAction(INTENT_ACTION_NOTIFY_SESSION_STOPPED);
final long identity = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5bb4a8a..121ab2c 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3771,6 +3771,7 @@
// Shell calls back into Core with the entry bounds to be applied with startWCT.
final Transition enterPipTransition = new Transition(TRANSIT_PIP,
0 /* flags */, getTransitionController(), mWindowManager.mSyncEngine);
+ r.setPictureInPictureParams(params);
enterPipTransition.setPipActivity(r);
r.mAutoEnteringPip = isAutoEnter;
getTransitionController().startCollectOrQueue(enterPipTransition, (deferred) -> {
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 643ee4a..62e5b9a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -2007,6 +2007,25 @@
}
@Test
+ public void testCanInterruptNonRingtoneInsistentBuzzWithOtherBuzzyNotification() {
+ NotificationRecord r = getInsistentBuzzyNotification();
+ mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+ verifyVibrateLooped();
+ assertTrue(r.isInterruptive());
+ assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+ Mockito.reset(mVibrator);
+
+ // New buzzy notification stops previous looping vibration
+ NotificationRecord interrupter = getBuzzyOtherNotification();
+ mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS);
+ verifyStopVibrate();
+ // And then vibrates itself
+ verifyVibrate(1);
+ assertTrue(interrupter.isInterruptive());
+ assertNotEquals(-1, interrupter.getLastAudiblyAlertedMs());
+ }
+
+ @Test
public void testRingtoneInsistentBeep_doesNotBlockFutureSoundsOnceStopped() throws Exception {
NotificationChannel ringtoneChannel =
new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index 65e8e13..ddf5ef2 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -463,8 +463,9 @@
public String toString() {
return "CarrierRestrictionRules(allowed:" + mAllowedCarriers + ", excluded:"
+ mExcludedCarriers + ", default:" + mCarrierRestrictionDefault
- + ", MultiSim policy:" + mMultiSimPolicy + getCarrierInfoList() +
- " mIsCarrierLockInfoSupported = " + mUseCarrierLockInfo + ")";
+ + ", MultiSim policy:" + mMultiSimPolicy + getCarrierInfoList()
+ + ", mIsCarrierLockInfoSupported = " + mUseCarrierLockInfo
+ + getCarrierRestrictionStatusToLog() + ")";
}
private String getCarrierInfoList() {
@@ -476,6 +477,13 @@
}
}
+ private String getCarrierRestrictionStatusToLog() {
+ if(android.os.Build.isDebuggable()) {
+ return ", CarrierRestrictionStatus = " + mCarrierRestrictionStatus;
+ }
+ return "";
+ }
+
/**
* Builder for a {@link CarrierRestrictionRules}.
*/
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
index 359eb35..5012c23 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
@@ -84,6 +84,7 @@
content.addView(enableSyncButton,
new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.RIGHT | Gravity.BOTTOM));
+ content.setFitsSystemWindows(true);
setContentView(content);
mSv.setZOrderOnTop(false);
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
index 73e0163..4119ea2 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
@@ -37,6 +37,7 @@
protected void onCreate(Bundle savedInstanceState) {
FrameLayout content = new FrameLayout(this);
+ content.setFitsSystemWindows(true);
super.onCreate(savedInstanceState);
mView = new SurfaceView(this);
content.addView(mView, new FrameLayout.LayoutParams(
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
index ac7dc9e..5287068 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
@@ -88,6 +88,7 @@
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout content = new LinearLayout(this);
+ content.setFitsSystemWindows(true);
mLocalSurfaceView = new SurfaceView(this);
content.addView(mLocalSurfaceView, new LinearLayout.LayoutParams(
500, 500, Gravity.CENTER_HORIZONTAL | Gravity.TOP));