Backup & Restore support for quick affordances.
Adds support for backup & restore of the currently-selected quick
affordances on the lock screen.
Only supports user 0 for now. A future CL will add support for additional users since the feature is broken for multi-user anyway (see b/260251307).
Fix: 258278136
Test: added test case. Manually verified, using the instructions from
go/localtransport that selecting some affordances, then triggering a
backup, then changing the selections and triggering a restore properly
restores the initial set of selections.
Change-Id: I9f2a51287a7467af95e7824f2b1804baba962099
diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
index 5616a00..621b99d 100644
--- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
+++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt
@@ -29,13 +29,15 @@
import android.util.Log
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper
import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
+import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper
import com.android.systemui.people.widget.PeopleBackupHelper
/**
* Helper for backing up elements in SystemUI
*
- * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI.
- * The helper can be used to back up any element that is stored in [Context.getFilesDir].
+ * This helper is invoked by BackupManager whenever a backup or restore is required in SystemUI. The
+ * helper can be used to back up any element that is stored in [Context.getFilesDir] or
+ * [Context.getSharedPreferences].
*
* After restoring is done, a [ACTION_RESTORE_FINISHED] intent will be send to SystemUI user 0,
* indicating that restoring is finished for a given user.
@@ -47,9 +49,11 @@
internal const val CONTROLS = ControlsFavoritePersistenceWrapper.FILE_NAME
private const val NO_OVERWRITE_FILES_BACKUP_KEY = "systemui.files_no_overwrite"
private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences"
+ private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY =
+ "systemui.keyguard.quickaffordance.shared_preferences"
val controlsDataLock = Any()
const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED"
- private const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
+ const val PERMISSION_SELF = "com.android.systemui.permission.SELF"
}
override fun onCreate(userHandle: UserHandle, operationType: Int) {
@@ -67,17 +71,27 @@
}
val keys = PeopleBackupHelper.getFilesToBackup()
- addHelper(PEOPLE_TILES_BACKUP_KEY, PeopleBackupHelper(
- this, userHandle, keys.toTypedArray()))
+ addHelper(
+ PEOPLE_TILES_BACKUP_KEY,
+ PeopleBackupHelper(this, userHandle, keys.toTypedArray())
+ )
+ addHelper(
+ KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY,
+ KeyguardQuickAffordanceBackupHelper(
+ context = this,
+ userId = userHandle.identifier,
+ ),
+ )
}
override fun onRestoreFinished() {
super.onRestoreFinished()
- val intent = Intent(ACTION_RESTORE_FINISHED).apply {
- `package` = packageName
- putExtra(Intent.EXTRA_USER_ID, userId)
- flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
- }
+ val intent =
+ Intent(ACTION_RESTORE_FINISHED).apply {
+ `package` = packageName
+ putExtra(Intent.EXTRA_USER_ID, userId)
+ flags = Intent.FLAG_RECEIVER_REGISTERED_ONLY
+ }
sendBroadcastAsUser(intent, UserHandle.SYSTEM, PERMISSION_SELF)
}
@@ -90,7 +104,9 @@
* @property lock a lock to hold while backing up and restoring the files.
* @property context the context of the [BackupAgent]
* @property fileNamesAndPostProcess a map from the filenames to back up and the post processing
+ * ```
* actions to take
+ * ```
*/
private class NoOverwriteFileBackupHelper(
val lock: Any,
@@ -115,23 +131,23 @@
data: BackupDataOutput?,
newState: ParcelFileDescriptor?
) {
- synchronized(lock) {
- super.performBackup(oldState, data, newState)
- }
+ synchronized(lock) { super.performBackup(oldState, data, newState) }
}
}
}
+
private fun getPPControlsFile(context: Context): () -> Unit {
return {
val filesDir = context.filesDir
val file = Environment.buildPath(filesDir, BackupHelper.CONTROLS)
if (file.exists()) {
- val dest = Environment.buildPath(filesDir,
- AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
+ val dest =
+ Environment.buildPath(filesDir, AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
file.copyTo(dest)
val jobScheduler = context.getSystemService(JobScheduler::class.java)
jobScheduler?.schedule(
- AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context))
+ AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context)
+ )
}
}
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
index 537cbc5..a0a892d 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/BroadcastDispatcher.kt
@@ -64,8 +64,9 @@
* from SystemUI. That way the number of calls to [BroadcastReceiver.onReceive] can be reduced for
* a given broadcast.
*
- * Use only for IntentFilters with actions and optionally categories. It does not support,
- * permissions, schemes, data types, data authorities or priority different than 0.
+ * Use only for IntentFilters with actions and optionally categories. It does not support schemes,
+ * data types, data authorities or priority different than 0.
+ *
* Cannot be used for getting sticky broadcasts (either as return of registering or as re-delivery).
* Broadcast handling may be asynchronous *without* calling goAsync(), as it's running within sysui
* and doesn't need to worry about being killed.
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
index b29cf45..4f37e5f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -18,9 +18,11 @@
package com.android.systemui.keyguard.data.quickaffordance
import android.content.Context
+import android.content.IntentFilter
import android.content.SharedPreferences
-import androidx.annotation.VisibleForTesting
import com.android.systemui.R
+import com.android.systemui.backup.BackupHelper
+import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
import com.android.systemui.dagger.SysUISingleton
@@ -28,14 +30,18 @@
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.onStart
/**
* Manages and provides access to the current "selections" of keyguard quick affordances, answering
* the question "which affordances should the keyguard show?".
*/
+@OptIn(ExperimentalCoroutinesApi::class)
@SysUISingleton
class KeyguardQuickAffordanceSelectionManager
@Inject
@@ -43,15 +49,10 @@
@Application context: Context,
private val userFileManager: UserFileManager,
private val userTracker: UserTracker,
+ broadcastDispatcher: BroadcastDispatcher,
) {
- private val sharedPrefs: SharedPreferences
- get() =
- userFileManager.getSharedPreferences(
- FILE_NAME,
- Context.MODE_PRIVATE,
- userTracker.userId,
- )
+ private var sharedPrefs: SharedPreferences = instantiateSharedPrefs()
private val userId: Flow<Int> = conflatedCallbackFlow {
val callback =
@@ -78,21 +79,54 @@
}
}
+ /**
+ * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an
+ * initial value.
+ */
+ private val backupRestorationEvents: Flow<Unit> =
+ broadcastDispatcher.broadcastFlow(
+ filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED),
+ flags = Context.RECEIVER_NOT_EXPORTED,
+ permission = BackupHelper.PERMISSION_SELF,
+ )
+
/** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
val selections: Flow<Map<String, List<String>>> =
- userId.flatMapLatest {
- conflatedCallbackFlow {
- val listener =
- SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
- trySend(getSelections())
- }
-
- sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
- send(getSelections())
-
- awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ combine(
+ userId,
+ backupRestorationEvents.onStart {
+ // We emit an initial event to make sure that the combine emits at least once,
+ // even
+ // if we never get a Backup & Restore restoration event (which is the most
+ // common
+ // case anyway as restoration really only happens on initial device setup).
+ emit(Unit)
+ }
+ ) { _, _ ->
}
- }
+ .flatMapLatest {
+ conflatedCallbackFlow {
+ // We want to instantiate a new SharedPreferences instance each time either the
+ // user
+ // ID changes or we have a backup & restore restoration event. The reason is
+ // that
+ // our sharedPrefs instance needs to be replaced with a new one as it depends on
+ // the
+ // user ID and when the B&R job completes, the backing file is replaced but the
+ // existing instance still has a stale in-memory cache.
+ sharedPrefs = instantiateSharedPrefs()
+
+ val listener =
+ SharedPreferences.OnSharedPreferenceChangeListener { _, _ ->
+ trySend(getSelections())
+ }
+
+ sharedPrefs.registerOnSharedPreferenceChangeListener(listener)
+ send(getSelections())
+
+ awaitClose { sharedPrefs.unregisterOnSharedPreferenceChangeListener(listener) }
+ }
+ }
/**
* Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
@@ -144,9 +178,17 @@
sharedPrefs.edit().putString(key, value).apply()
}
+ private fun instantiateSharedPrefs(): SharedPreferences {
+ return userFileManager.getSharedPreferences(
+ FILE_NAME,
+ Context.MODE_PRIVATE,
+ userTracker.userId,
+ )
+ }
+
companion object {
private const val TAG = "KeyguardQuickAffordanceSelectionManager"
- @VisibleForTesting const val FILE_NAME = "quick_affordance_selections"
+ const val FILE_NAME = "quick_affordance_selections"
private const val KEY_PREFIX_SLOT = "slot_"
private const val SLOT_AFFORDANCES_DELIMITER = ":"
private const val AFFORDANCE_DELIMITER = ","
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
new file mode 100644
index 0000000..0e865ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/backup/KeyguardQuickAffordanceBackupHelper.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.keyguard.domain.backup
+
+import android.app.backup.SharedPreferencesBackupHelper
+import android.content.Context
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.settings.UserFileManagerImpl
+
+/** Handles backup & restore for keyguard quick affordances. */
+class KeyguardQuickAffordanceBackupHelper(
+ context: Context,
+ userId: Int,
+) :
+ SharedPreferencesBackupHelper(
+ context,
+ if (UserFileManagerImpl.isPrimaryUser(userId)) {
+ KeyguardQuickAffordanceSelectionManager.FILE_NAME
+ } else {
+ UserFileManagerImpl.secondaryUserFile(
+ context = context,
+ fileName = KeyguardQuickAffordanceSelectionManager.FILE_NAME,
+ directoryName = UserFileManagerImpl.SHARED_PREFS,
+ userId = userId,
+ )
+ .also { UserFileManagerImpl.ensureParentDirExists(it) }
+ .toString()
+ }
+ )
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
index d450afa..bfba6df 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
@@ -35,12 +35,14 @@
import javax.inject.Inject
/**
- * Implementation for retrieving file paths for file storage of system and secondary users.
- * Files lie in {File Directory}/UserFileManager/{User Id} for secondary user.
- * For system user, we use the conventional {File Directory}
+ * Implementation for retrieving file paths for file storage of system and secondary users. Files
+ * lie in {File Directory}/UserFileManager/{User Id} for secondary user. For system user, we use the
+ * conventional {File Directory}
*/
@SysUISingleton
-class UserFileManagerImpl @Inject constructor(
+class UserFileManagerImpl
+@Inject
+constructor(
// Context of system process and system user.
private val context: Context,
val userManager: UserManager,
@@ -49,80 +51,114 @@
) : UserFileManager, CoreStartable {
companion object {
private const val FILES = "files"
- @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs"
+ const val SHARED_PREFS = "shared_prefs"
@VisibleForTesting internal const val ID = "UserFileManager"
- }
- private val broadcastReceiver = object : BroadcastReceiver() {
+ /** Returns `true` if the given user ID is that for the primary/system user. */
+ fun isPrimaryUser(userId: Int): Boolean {
+ return UserHandle(userId).isSystem
+ }
+
/**
- * Listen to Intent.ACTION_USER_REMOVED to clear user data.
+ * Returns a [File] pointing to the correct path for a secondary user ID.
+ *
+ * Note that there is no check for the type of user. This should only be called for
+ * secondary users, never for the system user. For that, make sure to call [isPrimaryUser].
+ *
+ * Note also that there is no guarantee that the parent directory structure for the file
+ * exists on disk. For that, call [ensureParentDirExists].
+ *
+ * @param context The context
+ * @param fileName The name of the file
+ * @param directoryName The name of the directory that would contain the file
+ * @param userId The ID of the user to build a file path for
*/
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action == Intent.ACTION_USER_REMOVED) {
- clearDeletedUserData()
+ fun secondaryUserFile(
+ context: Context,
+ fileName: String,
+ directoryName: String,
+ userId: Int,
+ ): File {
+ return Environment.buildPath(
+ context.filesDir,
+ ID,
+ userId.toString(),
+ directoryName,
+ fileName,
+ )
+ }
+
+ /**
+ * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
+ * recursively.
+ */
+ fun ensureParentDirExists(file: File) {
+ val parent = file.parentFile
+ if (!parent.exists()) {
+ if (!parent.mkdirs()) {
+ Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
+ }
}
}
}
- /**
- * Poll for user-specific directories to delete upon start up.
- */
+ private val broadcastReceiver =
+ object : BroadcastReceiver() {
+ /** Listen to Intent.ACTION_USER_REMOVED to clear user data. */
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == Intent.ACTION_USER_REMOVED) {
+ clearDeletedUserData()
+ }
+ }
+ }
+
+ /** Poll for user-specific directories to delete upon start up. */
override fun start() {
clearDeletedUserData()
- val filter = IntentFilter().apply {
- addAction(Intent.ACTION_USER_REMOVED)
- }
+ val filter = IntentFilter().apply { addAction(Intent.ACTION_USER_REMOVED) }
broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor)
}
- /**
- * Return the file based on current user.
- */
+ /** Return the file based on current user. */
override fun getFile(fileName: String, userId: Int): File {
- return if (UserHandle(userId).isSystem) {
- Environment.buildPath(
- context.filesDir,
- fileName
- )
+ return if (isPrimaryUser(userId)) {
+ Environment.buildPath(context.filesDir, fileName)
} else {
- val secondaryFile = Environment.buildPath(
- context.filesDir,
- ID,
- userId.toString(),
- FILES,
- fileName
- )
+ val secondaryFile =
+ secondaryUserFile(
+ context = context,
+ userId = userId,
+ directoryName = FILES,
+ fileName = fileName,
+ )
ensureParentDirExists(secondaryFile)
secondaryFile
}
}
- /**
- * Get shared preferences from user.
- */
+ /** Get shared preferences from user. */
override fun getSharedPreferences(
fileName: String,
@Context.PreferencesMode mode: Int,
userId: Int
): SharedPreferences {
- if (UserHandle(userId).isSystem) {
+ if (isPrimaryUser(userId)) {
return context.getSharedPreferences(fileName, mode)
}
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- ID,
- userId.toString(),
- SHARED_PREFS,
- fileName
- )
+
+ val secondaryUserDir =
+ secondaryUserFile(
+ context = context,
+ fileName = fileName,
+ directoryName = SHARED_PREFS,
+ userId = userId,
+ )
ensureParentDirExists(secondaryUserDir)
return context.getSharedPreferences(secondaryUserDir, mode)
}
- /**
- * Remove dirs for deleted users.
- */
+ /** Remove dirs for deleted users. */
@VisibleForTesting
internal fun clearDeletedUserData() {
backgroundExecutor.execute {
@@ -133,10 +169,11 @@
dirsToDelete.forEach { dir ->
try {
- val dirToDelete = Environment.buildPath(
- file,
- dir,
- )
+ val dirToDelete =
+ Environment.buildPath(
+ file,
+ dir,
+ )
dirToDelete.deleteRecursively()
} catch (e: Exception) {
Log.e(ID, "Deletion failed.", e)
@@ -144,18 +181,4 @@
}
}
}
-
- /**
- * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
- * recursively.
- */
- @VisibleForTesting
- internal fun ensureParentDirExists(file: File) {
- val parent = file.parentFile
- if (!parent.exists()) {
- if (!parent.mkdirs()) {
- Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
- }
- }
- }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
index cedde58..f451b3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardQuickAffordanceProviderTest.kt
@@ -89,6 +89,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
index 8ef921e..552b8cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceLegacySettingSyncerTest.kt
@@ -89,6 +89,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = FakeUserTracker(),
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
settings = FakeSettings()
settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
index d8ee9f1..6a2376b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.keyguard.data.quickaffordance
+import android.content.Intent
import android.content.SharedPreferences
import android.content.pm.UserInfo
import androidx.test.filters.SmallTest
@@ -27,10 +28,15 @@
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.toList
import kotlinx.coroutines.launch
import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -38,8 +44,12 @@
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import org.mockito.Mock
+import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
+@OptIn(ExperimentalCoroutinesApi::class)
@SmallTest
@RunWith(JUnit4::class)
class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
@@ -60,15 +70,23 @@
sharedPrefs.getOrPut(userId) { FakeSharedPreferences() }
}
userTracker = FakeUserTracker()
+ val dispatcher = UnconfinedTestDispatcher()
+ Dispatchers.setMain(dispatcher)
underTest =
KeyguardQuickAffordanceSelectionManager(
context = context,
userFileManager = userFileManager,
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
}
+ @After
+ fun tearDown() {
+ Dispatchers.resetMain()
+ }
+
@Test
fun setSelections() = runTest {
overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
@@ -318,6 +336,22 @@
job.cancel()
}
+ @Test
+ fun `responds to backup and restore by reloading the selections from disk`() = runTest {
+ overrideResource(R.array.config_keyguardQuickAffordanceDefaults, arrayOf<String>())
+ val affordanceIdsBySlotId = mutableListOf<Map<String, List<String>>>()
+ val job =
+ launch(UnconfinedTestDispatcher()) {
+ underTest.selections.toList(affordanceIdsBySlotId)
+ }
+ clearInvocations(userFileManager)
+
+ fakeBroadcastDispatcher.registeredReceivers.firstOrNull()?.onReceive(context, Intent())
+
+ verify(userFileManager, atLeastOnce()).getSharedPreferences(anyString(), anyInt(), anyInt())
+ job.cancel()
+ }
+
private fun assertSelections(
observed: Map<String, List<String>>?,
expected: Map<String, List<String>>,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 5c75417..652fae9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -76,6 +76,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = FakeUserTracker(),
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index c2650ec..ba7c40b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -252,6 +252,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index b790306..8d0c4ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -113,6 +113,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index 8b166bd..32849cd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -136,6 +136,7 @@
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
+ broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
index 6d9b01e..020a866 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
@@ -50,24 +50,20 @@
lateinit var userFileManager: UserFileManagerImpl
lateinit var backgroundExecutor: FakeExecutor
- @Mock
- lateinit var userManager: UserManager
- @Mock
- lateinit var broadcastDispatcher: BroadcastDispatcher
+ @Mock lateinit var userManager: UserManager
+ @Mock lateinit var broadcastDispatcher: BroadcastDispatcher
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
backgroundExecutor = FakeExecutor(FakeSystemClock())
- userFileManager = UserFileManagerImpl(context, userManager,
- broadcastDispatcher, backgroundExecutor)
+ userFileManager =
+ UserFileManagerImpl(context, userManager, broadcastDispatcher, backgroundExecutor)
}
@After
fun end() {
- val dir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID)
+ val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID)
dir.deleteRecursively()
}
@@ -82,13 +78,14 @@
@Test
fun testGetSharedPreferences() {
val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11)
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- UserFileManagerImpl.SHARED_PREFS,
- TEST_FILE_NAME
- )
+ val secondaryUserDir =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ UserFileManagerImpl.SHARED_PREFS,
+ TEST_FILE_NAME
+ )
assertThat(secondarySharedPref).isNotNull()
assertThat(secondaryUserDir.exists())
@@ -101,32 +98,35 @@
val userFileManager = spy(userFileManager)
userFileManager.start()
verify(userFileManager).clearDeletedUserData()
- verify(broadcastDispatcher).registerReceiver(any(BroadcastReceiver::class.java),
- any(IntentFilter::class.java),
- any(Executor::class.java), isNull(), eq(Context.RECEIVER_EXPORTED), isNull())
+ verify(broadcastDispatcher)
+ .registerReceiver(
+ any(BroadcastReceiver::class.java),
+ any(IntentFilter::class.java),
+ any(Executor::class.java),
+ isNull(),
+ eq(Context.RECEIVER_EXPORTED),
+ isNull()
+ )
}
@Test
fun testClearDeletedUserData() {
- val dir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files"
- )
+ val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID, "11", "files")
dir.mkdirs()
- val file = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files",
- TEST_FILE_NAME
- )
- val secondaryUserDir = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- )
+ val file =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files",
+ TEST_FILE_NAME
+ )
+ val secondaryUserDir =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ )
file.createNewFile()
assertThat(secondaryUserDir.exists()).isTrue()
assertThat(file.exists()).isTrue()
@@ -139,15 +139,16 @@
@Test
fun testEnsureParentDirExists() {
- val file = Environment.buildPath(
- context.filesDir,
- UserFileManagerImpl.ID,
- "11",
- "files",
- TEST_FILE_NAME
- )
+ val file =
+ Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files",
+ TEST_FILE_NAME
+ )
assertThat(file.parentFile.exists()).isFalse()
- userFileManager.ensureParentDirExists(file)
+ UserFileManagerImpl.ensureParentDirExists(file)
assertThat(file.parentFile.exists()).isTrue()
}
}