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:
@@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,14 +35,14 @@ 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,
|
||||||
executor: Executor
|
executor: Executor
|
||||||
): this(ControlsFavoritePersistenceWrapper(file, executor))
|
) : this(ControlsFavoritePersistenceWrapper(file, executor))
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val AUXILIARY_FILE_NAME = "aux_controls_favorites.xml"
|
const val AUXILIARY_FILE_NAME = "aux_controls_favorites.xml"
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user