Merge "Backup and restore - Fix non-system users" into tm-qpr-dev am: cb5228cf3f

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21388355

Change-Id: If5a2632369de72478201374c48f4935492fe2e87
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Matt Pietal
2023-02-15 15:13:40 +00:00
committed by Automerger Merge Worker
6 changed files with 249 additions and 202 deletions

View File

@@ -31,6 +31,7 @@ import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper
import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper
import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper
import com.android.systemui.people.widget.PeopleBackupHelper import com.android.systemui.people.widget.PeopleBackupHelper
import com.android.systemui.settings.UserFileManagerImpl
/** /**
* Helper for backing up elements in SystemUI * Helper for backing up elements in SystemUI
@@ -58,17 +59,8 @@ open class BackupHelper : BackupAgentHelper() {
override fun onCreate(userHandle: UserHandle, operationType: Int) { override fun onCreate(userHandle: UserHandle, operationType: Int) {
super.onCreate() super.onCreate()
// The map in mapOf is guaranteed to be order preserving
val controlsMap = mapOf(CONTROLS to getPPControlsFile(this))
NoOverwriteFileBackupHelper(controlsDataLock, this, controlsMap).also {
addHelper(NO_OVERWRITE_FILES_BACKUP_KEY, it)
}
// Conversations widgets backup only works for system user, because widgets' information is addControlsHelper(userHandle.identifier)
// stored in system user's SharedPreferences files and we can't open those from other users.
if (!userHandle.isSystem) {
return
}
val keys = PeopleBackupHelper.getFilesToBackup() val keys = PeopleBackupHelper.getFilesToBackup()
addHelper( addHelper(
@@ -95,6 +87,18 @@ open class BackupHelper : BackupAgentHelper() {
sendBroadcastAsUser(intent, UserHandle.SYSTEM, PERMISSION_SELF) sendBroadcastAsUser(intent, UserHandle.SYSTEM, PERMISSION_SELF)
} }
private fun addControlsHelper(userId: Int) {
val file = UserFileManagerImpl.createFile(
userId = userId,
fileName = CONTROLS,
)
// The map in mapOf is guaranteed to be order preserving
val controlsMap = mapOf(file.getPath() to getPPControlsFile(this, userId))
NoOverwriteFileBackupHelper(controlsDataLock, this, controlsMap).also {
addHelper(NO_OVERWRITE_FILES_BACKUP_KEY, it)
}
}
/** /**
* Helper class for restoring files ONLY if they are not present. * Helper class for restoring files ONLY if they are not present.
* *
@@ -136,17 +140,21 @@ open class BackupHelper : BackupAgentHelper() {
} }
} }
private fun getPPControlsFile(context: Context): () -> Unit { private fun getPPControlsFile(context: Context, userId: Int): () -> Unit {
return { return {
val filesDir = context.filesDir val file = UserFileManagerImpl.createFile(
val file = Environment.buildPath(filesDir, BackupHelper.CONTROLS) userId = userId,
fileName = BackupHelper.CONTROLS,
)
if (file.exists()) { if (file.exists()) {
val dest = val dest = UserFileManagerImpl.createFile(
Environment.buildPath(filesDir, AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME) userId = userId,
fileName = AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME,
)
file.copyTo(dest) file.copyTo(dest)
val jobScheduler = context.getSystemService(JobScheduler::class.java) val jobScheduler = context.getSystemService(JobScheduler::class.java)
jobScheduler?.schedule( jobScheduler?.schedule(
AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context) AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context, userId)
) )
} }
} }

View File

@@ -21,8 +21,10 @@ import android.app.job.JobParameters
import android.app.job.JobService import android.app.job.JobService
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.os.PersistableBundle
import com.android.internal.annotations.VisibleForTesting import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.backup.BackupHelper import com.android.systemui.backup.BackupHelper
import com.android.systemui.settings.UserFileManagerImpl
import java.io.File import java.io.File
import java.util.concurrent.Executor import java.util.concurrent.Executor
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@@ -33,9 +35,9 @@ import java.util.concurrent.TimeUnit
* This file is a copy of the `controls_favorites.xml` file restored from a back up. It is used to * This file is a copy of the `controls_favorites.xml` file restored from a back up. It is used to
* keep track of controls that were restored but its corresponding app has not been installed yet. * keep track of controls that were restored but its corresponding app has not been installed yet.
*/ */
class AuxiliaryPersistenceWrapper @VisibleForTesting internal constructor( class AuxiliaryPersistenceWrapper
wrapper: ControlsFavoritePersistenceWrapper @VisibleForTesting
) { internal constructor(wrapper: ControlsFavoritePersistenceWrapper) {
constructor( constructor(
file: File, file: File,
@@ -48,9 +50,7 @@ class AuxiliaryPersistenceWrapper @VisibleForTesting internal constructor(
private var persistenceWrapper: ControlsFavoritePersistenceWrapper = wrapper private var persistenceWrapper: ControlsFavoritePersistenceWrapper = wrapper
/** /** Access the current list of favorites as tracked by the auxiliary file */
* Access the current list of favorites as tracked by the auxiliary file
*/
var favorites: List<StructureInfo> = emptyList() var favorites: List<StructureInfo> = emptyList()
private set private set
@@ -73,7 +73,8 @@ class AuxiliaryPersistenceWrapper @VisibleForTesting internal constructor(
* exist, it will be initialized to an empty list. * exist, it will be initialized to an empty list.
*/ */
fun initialize() { fun initialize() {
favorites = if (persistenceWrapper.fileExists) { favorites =
if (persistenceWrapper.fileExists) {
persistenceWrapper.readFavorites() persistenceWrapper.readFavorites()
} else { } else {
emptyList() emptyList()
@@ -83,8 +84,8 @@ class AuxiliaryPersistenceWrapper @VisibleForTesting internal constructor(
/** /**
* Gets the list of favorite controls as persisted in the auxiliary file for a given component. * Gets the list of favorite controls as persisted in the auxiliary file for a given component.
* *
* When the favorites for that application are returned, they will be removed from the * When the favorites for that application are returned, they will be removed from the auxiliary
* auxiliary file immediately, so they won't be retrieved again. * file immediately, so they won't be retrieved again.
* @param componentName the name of the service that provided the controls * @param componentName the name of the service that provided the controls
* @return a list of structures with favorites * @return a list of structures with favorites
*/ */
@@ -103,20 +104,20 @@ class AuxiliaryPersistenceWrapper @VisibleForTesting internal constructor(
} }
} }
/** /** [JobService] to delete the auxiliary file after a week. */
* [JobService] to delete the auxiliary file after a week.
*/
class DeletionJobService : JobService() { class DeletionJobService : JobService() {
companion object { companion object {
@VisibleForTesting @VisibleForTesting internal val DELETE_FILE_JOB_ID = 1000
internal val DELETE_FILE_JOB_ID = 1000 @VisibleForTesting internal val USER = "USER"
private val WEEK_IN_MILLIS = TimeUnit.DAYS.toMillis(7) private val WEEK_IN_MILLIS = TimeUnit.DAYS.toMillis(7)
fun getJobForContext(context: Context): JobInfo { fun getJobForContext(context: Context, targetUserId: Int): JobInfo {
val jobId = DELETE_FILE_JOB_ID + context.userId val jobId = DELETE_FILE_JOB_ID + context.userId
val componentName = ComponentName(context, DeletionJobService::class.java) val componentName = ComponentName(context, DeletionJobService::class.java)
val bundle = PersistableBundle().also { it.putInt(USER, targetUserId) }
return JobInfo.Builder(jobId, componentName) return JobInfo.Builder(jobId, componentName)
.setMinimumLatency(WEEK_IN_MILLIS) .setMinimumLatency(WEEK_IN_MILLIS)
.setPersisted(true) .setPersisted(true)
.setExtras(bundle)
.build() .build()
} }
} }
@@ -127,8 +128,14 @@ class AuxiliaryPersistenceWrapper @VisibleForTesting internal constructor(
} }
override fun onStartJob(params: JobParameters): Boolean { override fun onStartJob(params: JobParameters): Boolean {
val userId = params.getExtras()?.getInt(USER, 0) ?: 0
synchronized(BackupHelper.controlsDataLock) { synchronized(BackupHelper.controlsDataLock) {
baseContext.deleteFile(AUXILIARY_FILE_NAME) val file =
UserFileManagerImpl.createFile(
userId = userId,
fileName = AUXILIARY_FILE_NAME,
)
baseContext.deleteFile(file.getPath())
} }
return false return false
} }

View File

@@ -29,16 +29,9 @@ class KeyguardQuickAffordanceBackupHelper(
) : ) :
SharedPreferencesBackupHelper( SharedPreferencesBackupHelper(
context, context,
if (UserFileManagerImpl.isPrimaryUser(userId)) { UserFileManagerImpl.createFile(
KeyguardQuickAffordanceSelectionManager.FILE_NAME
} else {
UserFileManagerImpl.secondaryUserFile(
context = context,
fileName = KeyguardQuickAffordanceSelectionManager.FILE_NAME,
directoryName = UserFileManagerImpl.SHARED_PREFS,
userId = userId, userId = userId,
fileName = KeyguardQuickAffordanceSelectionManager.FILE_NAME,
) )
.also { UserFileManagerImpl.ensureParentDirExists(it) } .getPath()
.toString()
}
) )

View File

@@ -32,73 +32,61 @@ import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.concurrency.DelayableExecutor import com.android.systemui.util.concurrency.DelayableExecutor
import java.io.File import java.io.File
import java.io.FilenameFilter
import javax.inject.Inject import javax.inject.Inject
/** /**
* Implementation for retrieving file paths for file storage of system and secondary users. Files * Implementation for retrieving file paths for file storage of system and secondary users. For
* lie in {File Directory}/UserFileManager/{User Id} for secondary user. For system user, we use the * non-system users, files will be prepended by a special prefix containing the user id.
* conventional {File Directory}
*/ */
@SysUISingleton @SysUISingleton
class UserFileManagerImpl class UserFileManagerImpl
@Inject @Inject
constructor( constructor(
// Context of system process and system user.
private val context: Context, private val context: Context,
val userManager: UserManager, val userManager: UserManager,
val broadcastDispatcher: BroadcastDispatcher, val broadcastDispatcher: BroadcastDispatcher,
@Background val backgroundExecutor: DelayableExecutor @Background val backgroundExecutor: DelayableExecutor
) : UserFileManager, CoreStartable { ) : UserFileManager, CoreStartable {
companion object { companion object {
private const val FILES = "files" private const val PREFIX = "__USER_"
private const val TAG = "UserFileManagerImpl"
const val ROOT_DIR = "UserFileManager"
const val FILES = "files"
const val SHARED_PREFS = "shared_prefs" const val SHARED_PREFS = "shared_prefs"
@VisibleForTesting internal const val ID = "UserFileManager"
/** Returns `true` if the given user ID is that for the primary/system user. */
fun isPrimaryUser(userId: Int): Boolean {
return UserHandle(userId).isSystem
}
/** /**
* Returns a [File] pointing to the correct path for a secondary user ID. * Returns a File object with a relative path, built from the userId for non-system users
*
* 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
*/ */
fun secondaryUserFile( fun createFile(fileName: String, userId: Int): File {
context: Context, return if (isSystemUser(userId)) {
fileName: String, File(fileName)
directoryName: String, } else {
userId: Int, File(getFilePrefix(userId) + fileName)
): File { }
}
fun createLegacyFile(context: Context, dir: String, fileName: String, userId: Int): File? {
return if (isSystemUser(userId)) {
null
} else {
return Environment.buildPath( return Environment.buildPath(
context.filesDir, context.filesDir,
ID, ROOT_DIR,
userId.toString(), userId.toString(),
directoryName, dir,
fileName, fileName
) )
} }
}
/** fun getFilePrefix(userId: Int): String {
* Checks to see if parent dir of the file exists. If it does not, we create the parent dirs return PREFIX + userId.toString() + "_"
* 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}")
}
} }
/** Returns `true` if the given user ID is that for the system user. */
private fun isSystemUser(userId: Int): Boolean {
return UserHandle(userId).isSystem
} }
} }
@@ -119,64 +107,87 @@ constructor(
broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor) broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor)
} }
/** Return the file based on current user. */ /**
* Return the file based on current user. Files for all users will exist in [context.filesDir],
* but non system user files will be prepended with [getFilePrefix].
*/
override fun getFile(fileName: String, userId: Int): File { override fun getFile(fileName: String, userId: Int): File {
return if (isPrimaryUser(userId)) { val file = File(context.filesDir, createFile(fileName, userId).path)
Environment.buildPath(context.filesDir, fileName) createLegacyFile(context, FILES, fileName, userId)?.run { migrate(file, this) }
} else { return file
val secondaryFile =
secondaryUserFile(
context = context,
userId = userId,
directoryName = FILES,
fileName = fileName,
)
ensureParentDirExists(secondaryFile)
secondaryFile
}
} }
/** Get shared preferences from user. */ /**
* Get shared preferences from user. Files for all users will exist in the shared_prefs dir, but
* non system user files will be prepended with [getFilePrefix].
*/
override fun getSharedPreferences( override fun getSharedPreferences(
fileName: String, fileName: String,
@Context.PreferencesMode mode: Int, @Context.PreferencesMode mode: Int,
userId: Int userId: Int
): SharedPreferences { ): SharedPreferences {
if (isPrimaryUser(userId)) { val file = createFile(fileName, userId)
return context.getSharedPreferences(fileName, mode) createLegacyFile(context, SHARED_PREFS, "$fileName.xml", userId)?.run {
val path = Environment.buildPath(context.dataDir, SHARED_PREFS, "${file.path}.xml")
migrate(path, this)
}
return context.getSharedPreferences(file.path, mode)
} }
val secondaryUserDir = /** Remove files for deleted users. */
secondaryUserFile(
context = context,
fileName = fileName,
directoryName = SHARED_PREFS,
userId = userId,
)
ensureParentDirExists(secondaryUserDir)
return context.getSharedPreferences(secondaryUserDir, mode)
}
/** Remove dirs for deleted users. */
@VisibleForTesting @VisibleForTesting
internal fun clearDeletedUserData() { internal fun clearDeletedUserData() {
backgroundExecutor.execute { backgroundExecutor.execute {
val file = Environment.buildPath(context.filesDir, ID) deleteFiles(context.filesDir)
if (!file.exists()) return@execute deleteFiles(File(context.dataDir, SHARED_PREFS))
val aliveUsers = userManager.aliveUsers.map { it.id.toString() } }
val dirsToDelete = file.list().filter { !aliveUsers.contains(it) } }
dirsToDelete.forEach { dir -> private fun migrate(dest: File, source: File) {
if (source.exists()) {
try { try {
val dirToDelete = val parent = source.getParentFile()
Environment.buildPath( source.renameTo(dest)
file,
dir, deleteParentDirsIfEmpty(parent)
)
dirToDelete.deleteRecursively()
} catch (e: Exception) { } catch (e: Exception) {
Log.e(ID, "Deletion failed.", e) Log.e(TAG, "Failed to rename and delete ${source.path}", e)
}
}
}
private fun deleteParentDirsIfEmpty(dir: File?) {
if (dir != null && dir.listFiles().size == 0) {
val priorParent = dir.parentFile
val isRoot = dir.name == ROOT_DIR
dir.delete()
if (!isRoot) {
deleteParentDirsIfEmpty(priorParent)
}
}
}
private fun deleteFiles(parent: File) {
val aliveUserFilePrefix = userManager.aliveUsers.map { getFilePrefix(it.id) }
val filesToDelete =
parent.listFiles(
FilenameFilter { _, name ->
name.startsWith(PREFIX) &&
aliveUserFilePrefix.filter { name.startsWith(it) }.isEmpty()
}
)
// This can happen in test environments
if (filesToDelete == null) {
Log.i(TAG, "Empty directory: ${parent.path}")
} else {
filesToDelete.forEach { file ->
Log.i(TAG, "Deleting file: ${file.path}")
try {
file.delete()
} catch (e: Exception) {
Log.e(TAG, "Deletion failed.", e)
} }
} }
} }

View File

@@ -18,9 +18,13 @@ package com.android.systemui.controls.controller
import android.app.job.JobParameters import android.app.job.JobParameters
import android.content.Context import android.content.Context
import android.os.PersistableBundle
import android.testing.AndroidTestingRunner import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper.DeletionJobService.Companion.USER
import com.android.systemui.util.mockito.whenever
import java.util.concurrent.TimeUnit
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue import org.junit.Assert.assertTrue
@@ -28,18 +32,15 @@ import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock import org.mockito.Mockito.mock
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations import org.mockito.MockitoAnnotations
import java.util.concurrent.TimeUnit
@SmallTest @SmallTest
@RunWith(AndroidTestingRunner::class) @RunWith(AndroidTestingRunner::class)
class DeletionJobServiceTest : SysuiTestCase() { class DeletionJobServiceTest : SysuiTestCase() {
@Mock @Mock private lateinit var context: Context
private lateinit var context: Context
private lateinit var service: AuxiliaryPersistenceWrapper.DeletionJobService private lateinit var service: AuxiliaryPersistenceWrapper.DeletionJobService
@@ -53,6 +54,10 @@ class DeletionJobServiceTest : SysuiTestCase() {
@Test @Test
fun testOnStartJob() { fun testOnStartJob() {
val bundle = PersistableBundle().also { it.putInt(USER, 0) }
val params = mock(JobParameters::class.java)
whenever(params.getExtras()).thenReturn(bundle)
// false means job is terminated // false means job is terminated
assertFalse(service.onStartJob(mock(JobParameters::class.java))) assertFalse(service.onStartJob(mock(JobParameters::class.java)))
verify(context).deleteFile(AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME) verify(context).deleteFile(AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME)
@@ -67,13 +72,17 @@ class DeletionJobServiceTest : SysuiTestCase() {
@Test @Test
fun testJobHasRightParameters() { fun testJobHasRightParameters() {
val userId = 10 val userId = 10
`when`(context.userId).thenReturn(userId) whenever(context.userId).thenReturn(userId)
`when`(context.packageName).thenReturn(mContext.packageName) whenever(context.packageName).thenReturn(mContext.packageName)
val jobInfo = AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context) val jobInfo =
AuxiliaryPersistenceWrapper.DeletionJobService.getJobForContext(context, userId)
assertEquals( assertEquals(
AuxiliaryPersistenceWrapper.DeletionJobService.DELETE_FILE_JOB_ID + userId, jobInfo.id) AuxiliaryPersistenceWrapper.DeletionJobService.DELETE_FILE_JOB_ID + userId,
jobInfo.id
)
assertTrue(jobInfo.isPersisted) assertTrue(jobInfo.isPersisted)
assertEquals(userId, jobInfo.getExtras().getInt(USER))
assertEquals(TimeUnit.DAYS.toMillis(7), jobInfo.minLatencyMillis) assertEquals(TimeUnit.DAYS.toMillis(7), jobInfo.minLatencyMillis)
} }
} }

View File

@@ -30,12 +30,14 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.eq
import com.android.systemui.util.time.FakeSystemClock import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import java.io.File
import java.nio.file.Files
import java.util.concurrent.Executor import java.util.concurrent.Executor
import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test
import org.junit.runner.RunWith import org.junit.runner.RunWith
import org.mockito.Mock import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.isNull import org.mockito.Mockito.isNull
import org.mockito.Mockito.spy import org.mockito.Mockito.spy
import org.mockito.Mockito.verify import org.mockito.Mockito.verify
@@ -61,36 +63,83 @@ class UserFileManagerImplTest : SysuiTestCase() {
UserFileManagerImpl(context, userManager, broadcastDispatcher, backgroundExecutor) UserFileManagerImpl(context, userManager, broadcastDispatcher, backgroundExecutor)
} }
@After
fun end() {
val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID)
dir.deleteRecursively()
}
@Test @Test
fun testGetFile() { fun testGetFile() {
assertThat(userFileManager.getFile(TEST_FILE_NAME, 0).path) assertThat(userFileManager.getFile(TEST_FILE_NAME, 0).path)
.isEqualTo("${context.filesDir}/$TEST_FILE_NAME") .isEqualTo("${context.filesDir}/$TEST_FILE_NAME")
assertThat(userFileManager.getFile(TEST_FILE_NAME, 11).path) assertThat(userFileManager.getFile(TEST_FILE_NAME, 11).path)
.isEqualTo("${context.filesDir}/${UserFileManagerImpl.ID}/11/files/$TEST_FILE_NAME") .isEqualTo("${context.filesDir}/__USER_11_$TEST_FILE_NAME")
} }
@Test @Test
fun testGetSharedPreferences() { fun testGetSharedPreferences() {
val primarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 0)
val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11) val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11)
val secondaryUserDir =
Environment.buildPath(
context.filesDir,
UserFileManagerImpl.ID,
"11",
UserFileManagerImpl.SHARED_PREFS,
TEST_FILE_NAME
)
assertThat(secondarySharedPref).isNotNull() assertThat(primarySharedPref).isNotEqualTo(secondarySharedPref)
assertThat(secondaryUserDir.exists())
assertThat(userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 0)) // Make sure these are different files
.isNotEqualTo(secondarySharedPref) primarySharedPref.edit().putString("TEST", "ABC").commit()
assertThat(secondarySharedPref.getString("TEST", null)).isNull()
context.deleteSharedPreferences("TEST")
context.deleteSharedPreferences("__USER_11_TEST")
}
@Test
fun testMigrateFile() {
val userId = 12
val fileName = "myFile.txt"
val fileContents = "TestingFile"
val legacyFile =
UserFileManagerImpl.createLegacyFile(
context,
UserFileManagerImpl.FILES,
fileName,
userId
)!!
// Write file to legacy area
Files.createDirectories(legacyFile.getParentFile().toPath())
Files.write(legacyFile.toPath(), fileContents.toByteArray())
assertThat(legacyFile.exists()).isTrue()
// getFile() should migrate the legacy file to the new location
val file = userFileManager.getFile(fileName, userId)
val newContents = String(Files.readAllBytes(file.toPath()))
assertThat(newContents).isEqualTo(fileContents)
assertThat(legacyFile.exists()).isFalse()
assertThat(File(context.filesDir, UserFileManagerImpl.ROOT_DIR).exists()).isFalse()
}
@Test
fun testMigrateSharedPrefs() {
val userId = 13
val fileName = "myFile"
val contents = "TestingSharedPrefs"
val legacyFile =
UserFileManagerImpl.createLegacyFile(
context,
UserFileManagerImpl.SHARED_PREFS,
"$fileName.xml",
userId
)!!
// Write a valid shared prefs xml file to legacy area
val tmpPrefs = context.getSharedPreferences("tmp", Context.MODE_PRIVATE)
tmpPrefs.edit().putString(contents, contents).commit()
Files.createDirectories(legacyFile.getParentFile().toPath())
val tmpFile =
Environment.buildPath(context.dataDir, UserFileManagerImpl.SHARED_PREFS, "tmp.xml")
tmpFile.renameTo(legacyFile)
assertThat(legacyFile.exists()).isTrue()
// getSharedpreferences() should migrate the legacy file to the new location
val prefs = userFileManager.getSharedPreferences(fileName, Context.MODE_PRIVATE, userId)
assertThat(prefs.getString(contents, "")).isEqualTo(contents)
assertThat(legacyFile.exists()).isFalse()
assertThat(File(context.filesDir, UserFileManagerImpl.ROOT_DIR).exists()).isFalse()
} }
@Test @Test
@@ -111,44 +160,14 @@ class UserFileManagerImplTest : SysuiTestCase() {
@Test @Test
fun testClearDeletedUserData() { fun testClearDeletedUserData() {
val dir = Environment.buildPath(context.filesDir, UserFileManagerImpl.ID, "11", "files") val file = userFileManager.getFile(TEST_FILE_NAME, 11)
dir.mkdirs()
val file =
Environment.buildPath(
context.filesDir,
UserFileManagerImpl.ID,
"11",
"files",
TEST_FILE_NAME
)
val secondaryUserDir =
Environment.buildPath(
context.filesDir,
UserFileManagerImpl.ID,
"11",
)
file.createNewFile() file.createNewFile()
assertThat(secondaryUserDir.exists()).isTrue()
assertThat(file.exists()).isTrue() assertThat(file.exists()).isTrue()
userFileManager.clearDeletedUserData() userFileManager.clearDeletedUserData()
assertThat(backgroundExecutor.runAllReady()).isGreaterThan(0) assertThat(backgroundExecutor.runAllReady()).isGreaterThan(0)
verify(userManager).aliveUsers verify(userManager, atLeastOnce()).aliveUsers
assertThat(secondaryUserDir.exists()).isFalse()
assertThat(file.exists()).isFalse() assertThat(file.exists()).isFalse()
} }
@Test
fun testEnsureParentDirExists() {
val file =
Environment.buildPath(
context.filesDir,
UserFileManagerImpl.ID,
"11",
"files",
TEST_FILE_NAME
)
assertThat(file.parentFile.exists()).isFalse()
UserFileManagerImpl.ensureParentDirExists(file)
assertThat(file.parentFile.exists()).isTrue()
}
} }