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
This commit is contained in:
Alejandro Nijamkin
2022-11-23 11:34:09 -08:00
parent 96f5e4321e
commit 94f82c678b
13 changed files with 316 additions and 149 deletions

View File

@@ -29,13 +29,15 @@ import android.os.UserHandle
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 @@ open class BackupHelper : BackupAgentHelper() {
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 @@ open class BackupHelper : BackupAgentHelper() {
}
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 @@ open class BackupHelper : BackupAgentHelper() {
* @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 @@ open class BackupHelper : BackupAgentHelper() {
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)
)
}
}
}
}

View File

@@ -64,8 +64,9 @@ private const val TAG = "BroadcastDispatcher"
* 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.

View File

@@ -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.dagger.qualifiers.Application
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 @@ constructor(
@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 @@ constructor(
}
}
/**
* 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 @@ constructor(
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 = ","

View File

@@ -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()
}
)

View File

@@ -35,12 +35,14 @@ import java.io.File
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 @@ class UserFileManagerImpl @Inject constructor(
) : 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 @@ class UserFileManagerImpl @Inject constructor(
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 @@ class UserFileManagerImpl @Inject constructor(
}
}
}
/**
* 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}")
}
}
}
}

View File

@@ -89,6 +89,7 @@ class KeyguardQuickAffordanceProviderTest : SysuiTestCase() {
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(

View File

@@ -89,6 +89,7 @@ class KeyguardQuickAffordanceLegacySettingSyncerTest : SysuiTestCase() {
.thenReturn(FakeSharedPreferences())
},
userTracker = FakeUserTracker(),
broadcastDispatcher = fakeBroadcastDispatcher,
)
settings = FakeSettings()
settings.putInt(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS, 0)

View File

@@ -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.settings.UserFileManager
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.junit.runners.JUnit4
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 @@ class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
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 @@ class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
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>>,

View File

@@ -76,6 +76,7 @@ class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
.thenReturn(FakeSharedPreferences())
},
userTracker = FakeUserTracker(),
broadcastDispatcher = fakeBroadcastDispatcher,
)
underTest =

View File

@@ -252,6 +252,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() {
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(

View File

@@ -113,6 +113,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(

View File

@@ -136,6 +136,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() {
.thenReturn(FakeSharedPreferences())
},
userTracker = userTracker,
broadcastDispatcher = fakeBroadcastDispatcher,
)
val quickAffordanceRepository =
KeyguardQuickAffordanceRepository(

View File

@@ -50,24 +50,20 @@ class UserFileManagerImplTest : SysuiTestCase() {
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 @@ class UserFileManagerImplTest : SysuiTestCase() {
@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 @@ class UserFileManagerImplTest : SysuiTestCase() {
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 @@ class UserFileManagerImplTest : SysuiTestCase() {
@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()
}
}