1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
|
/*
* 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 java.io.FilenameFilter
import javax.inject.Inject
/**
* Implementation for retrieving file paths for file storage of system and secondary users. For
* non-system users, files will be prepended by a special prefix containing the user id.
*/
@SysUISingleton
class UserFileManagerImpl
@Inject
constructor(
private val context: Context,
val userManager: UserManager,
val broadcastDispatcher: BroadcastDispatcher,
@Background val backgroundExecutor: DelayableExecutor
) : UserFileManager, CoreStartable {
companion object {
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"
/**
* Returns a File object with a relative path, built from the userId for non-system users
*/
fun createFile(fileName: String, userId: Int): File {
return if (isSystemUser(userId)) {
File(fileName)
} else {
File(getFilePrefix(userId) + fileName)
}
}
fun createLegacyFile(context: Context, dir: String, fileName: String, userId: Int): File? {
return if (isSystemUser(userId)) {
null
} else {
return Environment.buildPath(
context.filesDir,
ROOT_DIR,
userId.toString(),
dir,
fileName
)
}
}
fun getFilePrefix(userId: Int): String {
return PREFIX + userId.toString() + "_"
}
/** Returns `true` if the given user ID is that for the system user. */
private fun isSystemUser(userId: Int): Boolean {
return UserHandle(userId).isSystem
}
}
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. 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 {
val file = File(context.filesDir, createFile(fileName, userId).path)
createLegacyFile(context, FILES, fileName, userId)?.run { migrate(file, this) }
return file
}
/**
* 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(
fileName: String,
@Context.PreferencesMode mode: Int,
userId: Int
): SharedPreferences {
val file = createFile(fileName, userId)
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)
}
/** Remove files for deleted users. */
@VisibleForTesting
internal fun clearDeletedUserData() {
backgroundExecutor.execute {
deleteFiles(context.filesDir)
deleteFiles(File(context.dataDir, SHARED_PREFS))
}
}
private fun migrate(dest: File, source: File) {
if (source.exists()) {
try {
val parent = source.getParentFile()
source.renameTo(dest)
deleteParentDirsIfEmpty(parent)
} catch (e: Exception) {
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)
}
}
}
}
}
|