Loading packages/SystemUI/docs/user-file-manager.md 0 → 100644 +13 −0 Original line number Diff line number Diff line # UserFileManager This class is used to generate file paths and SharedPreferences that is compatible for multiple users in SystemUI. Due to constraints in SystemUI, we can only read/write files as the system user. Therefore, for secondary users, we want to store secondary user specific files into the system user directory. ## Handling User Removal This class will listen for Intent.ACTION_USER_REMOVED and remove directories that no longer corresponding to active users. Additionally, upon start up, the class will run the same query for deletion to ensure that there is no stale data. packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +3 −2 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import com.android.systemui.log.SessionTracker import com.android.systemui.media.RingtonePlayer import com.android.systemui.power.PowerUI import com.android.systemui.recents.Recents import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher import com.android.systemui.statusbar.notification.InstantAppNotifier import com.android.systemui.statusbar.phone.KeyguardLiftController Loading @@ -51,7 +52,7 @@ import dagger.multibindings.IntoMap /** * Collection of {@link CoreStartable}s that should be run on AOSP. */ @Module @Module(includes = [MultiUserUtilsModule::class]) abstract class SystemUICoreStartableModule { /** Inject into AuthController. */ @Binds Loading packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +2 −2 Original line number Diff line number Diff line Loading @@ -50,7 +50,7 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin; import com.android.systemui.privacy.PrivacyModule; import com.android.systemui.recents.Recents; import com.android.systemui.screenshot.dagger.ScreenshotModule; import com.android.systemui.settings.dagger.SettingsModule; import com.android.systemui.settings.dagger.MultiUserUtilsModule; import com.android.systemui.smartspace.dagger.SmartspaceModule; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; Loading Loading @@ -127,7 +127,7 @@ import dagger.Provides; QsFrameTranslateModule.class, ScreenshotModule.class, SensorModule.class, SettingsModule.class, MultiUserUtilsModule.class, SettingsUtilModule.class, SmartRepliesInflationModule.class, SmartspaceModule.class, Loading packages/SystemUI/src/com/android/systemui/settings/UserFileManager.kt 0 → 100644 +39 −0 Original line number Diff line number Diff line /* * 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.Context import android.content.SharedPreferences import java.io.File /** * Interface for retrieving file paths for file storage of system and secondary users. */ interface UserFileManager { /** * Return the file based on current user. */ fun getFile(fileName: String, userId: Int): File /** * Get shared preferences from user. */ fun getSharedPreferences( fileName: String, @Context.PreferencesMode mode: Int, userId: Int ): SharedPreferences } packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * 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" private const val SHARED_PREFS = "shared_prefs" 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 { Environment.buildPath( context.filesDir, ID, userId.toString(), FILES, fileName ) } } /** * 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 ) 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) } } } } } Loading
packages/SystemUI/docs/user-file-manager.md 0 → 100644 +13 −0 Original line number Diff line number Diff line # UserFileManager This class is used to generate file paths and SharedPreferences that is compatible for multiple users in SystemUI. Due to constraints in SystemUI, we can only read/write files as the system user. Therefore, for secondary users, we want to store secondary user specific files into the system user directory. ## Handling User Removal This class will listen for Intent.ACTION_USER_REMOVED and remove directories that no longer corresponding to active users. Additionally, upon start up, the class will run the same query for deletion to ensure that there is no stale data.
packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +3 −2 Original line number Diff line number Diff line Loading @@ -33,6 +33,7 @@ import com.android.systemui.log.SessionTracker import com.android.systemui.media.RingtonePlayer import com.android.systemui.power.PowerUI import com.android.systemui.recents.Recents import com.android.systemui.settings.dagger.MultiUserUtilsModule import com.android.systemui.shortcut.ShortcutKeyDispatcher import com.android.systemui.statusbar.notification.InstantAppNotifier import com.android.systemui.statusbar.phone.KeyguardLiftController Loading @@ -51,7 +52,7 @@ import dagger.multibindings.IntoMap /** * Collection of {@link CoreStartable}s that should be run on AOSP. */ @Module @Module(includes = [MultiUserUtilsModule::class]) abstract class SystemUICoreStartableModule { /** Inject into AuthController. */ @Binds Loading
packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +2 −2 Original line number Diff line number Diff line Loading @@ -50,7 +50,7 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin; import com.android.systemui.privacy.PrivacyModule; import com.android.systemui.recents.Recents; import com.android.systemui.screenshot.dagger.ScreenshotModule; import com.android.systemui.settings.dagger.SettingsModule; import com.android.systemui.settings.dagger.MultiUserUtilsModule; import com.android.systemui.smartspace.dagger.SmartspaceModule; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.NotificationLockscreenUserManager; Loading Loading @@ -127,7 +127,7 @@ import dagger.Provides; QsFrameTranslateModule.class, ScreenshotModule.class, SensorModule.class, SettingsModule.class, MultiUserUtilsModule.class, SettingsUtilModule.class, SmartRepliesInflationModule.class, SmartspaceModule.class, Loading
packages/SystemUI/src/com/android/systemui/settings/UserFileManager.kt 0 → 100644 +39 −0 Original line number Diff line number Diff line /* * 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.Context import android.content.SharedPreferences import java.io.File /** * Interface for retrieving file paths for file storage of system and secondary users. */ interface UserFileManager { /** * Return the file based on current user. */ fun getFile(fileName: String, userId: Int): File /** * Get shared preferences from user. */ fun getSharedPreferences( fileName: String, @Context.PreferencesMode mode: Int, userId: Int ): SharedPreferences }
packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt 0 → 100644 +144 −0 Original line number Diff line number Diff line /* * 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" private const val SHARED_PREFS = "shared_prefs" 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 { Environment.buildPath( context.filesDir, ID, userId.toString(), FILES, fileName ) } } /** * 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 ) 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) } } } } }