blob: ad073c073ed8dba4f98bf4b1c53c090174ddfe9e [file] [log] [blame]
/*
* 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.settings
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.SharedPreferences
import android.os.Environment
import android.os.UserHandle
import android.os.UserManager
import android.util.Log
import androidx.annotation.VisibleForTesting
import com.android.systemui.CoreStartable
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.util.concurrency.DelayableExecutor
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}
*/
@SysUISingleton
class UserFileManagerImpl @Inject constructor(
// Context of system process and system user.
val context: Context,
val userManager: UserManager,
val broadcastDispatcher: BroadcastDispatcher,
@Background val backgroundExecutor: DelayableExecutor
) : UserFileManager, CoreStartable(context) {
companion object {
private const val FILES = "files"
@VisibleForTesting internal const val SHARED_PREFS = "shared_prefs"
@VisibleForTesting internal const val ID = "UserFileManager"
}
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)
}
broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor)
}
/**
* 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
)
} else {
val secondaryFile = Environment.buildPath(
context.filesDir,
ID,
userId.toString(),
FILES,
fileName
)
ensureParentDirExists(secondaryFile)
secondaryFile
}
}
/**
* Get shared preferences from user.
*/
override fun getSharedPreferences(
fileName: String,
@Context.PreferencesMode mode: Int,
userId: Int
): SharedPreferences {
if (UserHandle(userId).isSystem) {
return context.getSharedPreferences(fileName, mode)
}
val secondaryUserDir = Environment.buildPath(
context.filesDir,
ID,
userId.toString(),
SHARED_PREFS,
fileName
)
ensureParentDirExists(secondaryUserDir)
return context.getSharedPreferences(secondaryUserDir, mode)
}
/**
* Remove dirs for deleted users.
*/
@VisibleForTesting
internal fun clearDeletedUserData() {
backgroundExecutor.execute {
val file = Environment.buildPath(context.filesDir, ID)
if (!file.exists()) return@execute
val aliveUsers = userManager.aliveUsers.map { it.id.toString() }
val dirsToDelete = file.list().filter { !aliveUsers.contains(it) }
dirsToDelete.forEach { dir ->
try {
val dirToDelete = Environment.buildPath(
file,
dir,
)
dirToDelete.deleteRecursively()
} catch (e: Exception) {
Log.e(ID, "Deletion failed.", e)
}
}
}
}
/**
* 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}")
}
}
}
}