Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1ff91194 authored by Aaron Liu's avatar Aaron Liu Committed by Android (Google) Code Review
Browse files

Merge "[MultiUser] Add file access wrapper." into tm-qpr-dev

parents 0273cc68 42af219b
Loading
Loading
Loading
Loading
+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.
+3 −2
Original line number Diff line number Diff line
@@ -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
@@ -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
+2 −2
Original line number Diff line number Diff line
@@ -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;
@@ -127,7 +127,7 @@ import dagger.Provides;
            QsFrameTranslateModule.class,
            ScreenshotModule.class,
            SensorModule.class,
            SettingsModule.class,
            MultiUserUtilsModule.class,
            SettingsUtilModule.class,
            SmartRepliesInflationModule.class,
            SmartspaceModule.class,
+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
}
+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