1 /* <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.settings 18 19 import android.content.BroadcastReceiver 20 import android.content.Context 21 import android.content.Intent 22 import android.content.IntentFilter 23 import android.content.SharedPreferences 24 import android.os.Environment 25 import android.os.UserHandle 26 import android.os.UserManager 27 import android.util.Log 28 import androidx.annotation.VisibleForTesting 29 import com.android.systemui.CoreStartable 30 import com.android.systemui.broadcast.BroadcastDispatcher 31 import com.android.systemui.dagger.SysUISingleton 32 import com.android.systemui.dagger.qualifiers.Background 33 import com.android.systemui.util.concurrency.DelayableExecutor 34 import java.io.File 35 import java.io.FilenameFilter 36 import javax.inject.Inject 37 38 /** 39 * Implementation for retrieving file paths for file storage of system and secondary users. For 40 * non-system users, files will be prepended by a special prefix containing the user id. 41 */ 42 @SysUISingleton 43 class UserFileManagerImpl 44 @Inject 45 constructor( 46 private val context: Context, 47 val userManager: UserManager, 48 val broadcastDispatcher: BroadcastDispatcher, 49 @Background val backgroundExecutor: DelayableExecutor 50 ) : UserFileManager, CoreStartable { 51 companion object { 52 private const val PREFIX = "__USER_" 53 private const val TAG = "UserFileManagerImpl" 54 const val ROOT_DIR = "UserFileManager" 55 const val FILES = "files" 56 const val SHARED_PREFS = "shared_prefs" 57 58 /** 59 * Returns a File object with a relative path, built from the userId for non-system users 60 */ 61 fun createFile(fileName: String, userId: Int): File { 62 return if (isSystemUser(userId)) { 63 File(fileName) 64 } else { 65 File(getFilePrefix(userId) + fileName) 66 } 67 } 68 69 fun createLegacyFile(context: Context, dir: String, fileName: String, userId: Int): File? { 70 return if (isSystemUser(userId)) { 71 null 72 } else { 73 return Environment.buildPath( 74 context.filesDir, 75 ROOT_DIR, 76 userId.toString(), 77 dir, 78 fileName 79 ) 80 } 81 } 82 83 fun getFilePrefix(userId: Int): String { 84 return PREFIX + userId.toString() + "_" 85 } 86 87 /** Returns `true` if the given user ID is that for the system user. */ 88 private fun isSystemUser(userId: Int): Boolean { 89 return UserHandle(userId).isSystem 90 } 91 } 92 93 private val broadcastReceiver = 94 object : BroadcastReceiver() { 95 /** Listen to Intent.ACTION_USER_REMOVED to clear user data. */ 96 override fun onReceive(context: Context, intent: Intent) { 97 if (intent.action == Intent.ACTION_USER_REMOVED) { 98 clearDeletedUserData() 99 } 100 } 101 } 102 103 /** Poll for user-specific directories to delete upon start up. */ 104 override fun start() { 105 clearDeletedUserData() 106 val filter = IntentFilter().apply { addAction(Intent.ACTION_USER_REMOVED) } 107 broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor) 108 } 109 110 /** 111 * Return the file based on current user. Files for all users will exist in [context.filesDir], 112 * but non system user files will be prepended with [getFilePrefix]. 113 */ 114 override fun getFile(fileName: String, userId: Int): File { 115 val file = File(context.filesDir, createFile(fileName, userId).path) 116 createLegacyFile(context, FILES, fileName, userId)?.run { migrate(file, this) } 117 return file 118 } 119 120 /** 121 * Get shared preferences from user. Files for all users will exist in the shared_prefs dir, but 122 * non system user files will be prepended with [getFilePrefix]. 123 */ 124 override fun getSharedPreferences( 125 fileName: String, 126 @Context.PreferencesMode mode: Int, 127 userId: Int 128 ): SharedPreferences { 129 val file = createFile(fileName, userId) 130 createLegacyFile(context, SHARED_PREFS, "$fileName.xml", userId)?.run { 131 val path = Environment.buildPath(context.dataDir, SHARED_PREFS, "${file.path}.xml") 132 migrate(path, this) 133 } 134 return context.getSharedPreferences(file.path, mode) 135 } 136 137 /** Remove files for deleted users. */ 138 @VisibleForTesting 139 internal fun clearDeletedUserData() { 140 backgroundExecutor.execute { 141 deleteFiles(context.filesDir) 142 deleteFiles(File(context.dataDir, SHARED_PREFS)) 143 } 144 } 145 146 private fun migrate(dest: File, source: File) { 147 if (source.exists()) { 148 try { 149 val parent = source.getParentFile() 150 source.renameTo(dest) 151 152 deleteParentDirsIfEmpty(parent) 153 } catch (e: Exception) { 154 Log.e(TAG, "Failed to rename and delete ${source.path}", e) 155 } 156 } 157 } 158 159 private fun deleteParentDirsIfEmpty(dir: File?) { 160 if (dir != null && dir.listFiles().size == 0) { 161 val priorParent = dir.parentFile 162 val isRoot = dir.name == ROOT_DIR 163 dir.delete() 164 165 if (!isRoot) { 166 deleteParentDirsIfEmpty(priorParent) 167 } 168 } 169 } 170 171 private fun deleteFiles(parent: File) { 172 val aliveUserFilePrefix = userManager.aliveUsers.map { getFilePrefix(it.id) } 173 val filesToDelete = 174 parent.listFiles( 175 FilenameFilter { _, name -> 176 name.startsWith(PREFIX) && 177 aliveUserFilePrefix.filter { name.startsWith(it) }.isEmpty() 178 } 179 ) 180 181 // This can happen in test environments 182 if (filesToDelete == null) { 183 Log.i(TAG, "Empty directory: ${parent.path}") 184 } else { 185 filesToDelete.forEach { file -> 186 Log.i(TAG, "Deleting file: ${file.path}") 187 try { 188 file.delete() 189 } catch (e: Exception) { 190 Log.e(TAG, "Deletion failed.", e) 191 } 192 } 193 } 194 } 195 } 196