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

Commit 5c0bb75d authored by Tetiana Meronyk's avatar Tetiana Meronyk
Browse files

Show other users in storage settings

Bug: 232969364
Test: croot && make RunSettingsRoboTests -j40 ROBOTEST_FILTER="com.android.settings.deviceinfo.storage.NonCurrentUserControllerTest"
Change-Id: I8d933bec8f12faa971684939b722cfae957f4953
parent c5bd4228
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -73,7 +73,7 @@
        android:order="108"/>
    <!-- Preference order 100~200 are 'ONLY' for storage category preferences above. -->
    <PreferenceCategory
        android:key="pref_secondary_users"
        android:key="pref_non_current_users"
        android:title="@string/storage_other_users"
        android:order="201" />
</PreferenceScreen>
+1 −1
Original line number Diff line number Diff line
@@ -92,7 +92,7 @@
        android:order="108"/>
    <!-- Preference order 100~200 are 'ONLY' for storage category preferences above. -->
    <PreferenceCategory
        android:key="pref_secondary_users"
        android:key="pref_non_current_users"
        android:title="@string/storage_other_users"
        android:order="201" />
</PreferenceScreen>
+34 −31
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.settings.deviceinfo;

import static java.util.Collections.EMPTY_LIST;

import android.app.settings.SettingsEnums;
import android.app.usage.StorageStatsManager;
import android.content.Context;
@@ -29,13 +31,15 @@ import android.view.View;
import androidx.annotation.VisibleForTesting;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
import com.android.settings.deviceinfo.storage.ManageStoragePreferenceController;
import com.android.settings.deviceinfo.storage.SecondaryUserController;
import com.android.settings.deviceinfo.storage.NonCurrentUserController;
import com.android.settings.deviceinfo.storage.StorageAsyncLoader;
import com.android.settings.deviceinfo.storage.StorageCacheHelper;
import com.android.settings.deviceinfo.storage.StorageEntry;
@@ -49,7 +53,6 @@ import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

/**
 * Storage Settings main UI is composed by 3 fragments:
@@ -70,6 +73,7 @@ public class StorageCategoryFragment extends DashboardFragment
    private static final String TAG = "StorageCategoryFrag";
    private static final String SELECTED_STORAGE_ENTRY_KEY = "selected_storage_entry_key";
    private static final String SUMMARY_PREF_KEY = "storage_summary";
    private static final String TARGET_PREFERENCE_GROUP_KEY = "pref_non_current_users";
    private static final int STORAGE_JOB_ID = 0;
    private static final int ICON_JOB_ID = 1;
    private static final int VOLUME_SIZE_JOB_ID = 2;
@@ -81,7 +85,7 @@ public class StorageCategoryFragment extends DashboardFragment
    private SparseArray<StorageAsyncLoader.StorageResult> mAppsResult;

    private StorageItemPreferenceController mPreferenceController;
    private List<AbstractPreferenceController> mSecondaryUsers;
    private List<NonCurrentUserController> mNonCurrentUsers;
    private boolean mIsWorkProfile;
    private int mUserId;
    private boolean mIsLoadedFromCache;
@@ -98,9 +102,9 @@ public class StorageCategoryFragment extends DashboardFragment
            return;
        }

        // To prevent flicker, hides secondary users preference.
        // To prevent flicker, hides non-current users preference.
        // onReceivedSizes will set it visible for private storage.
        setSecondaryUsersVisible(false);
        setNonCurrentUsersVisible(false);

        if (!mSelectedStorageEntry.isMounted()) {
            // Set null volume to hide category stats.
@@ -150,8 +154,8 @@ public class StorageCategoryFragment extends DashboardFragment
            if (mSelectedStorageEntry != null) {
                refreshUi(mSelectedStorageEntry);
            }
            updateSecondaryUserControllers(mSecondaryUsers, mAppsResult);
            setSecondaryUsersVisible(true);
            updateNonCurrentUserControllers(mNonCurrentUsers, mAppsResult);
            setNonCurrentUsersVisible(true);
        }
    }

@@ -217,17 +221,13 @@ public class StorageCategoryFragment extends DashboardFragment
        // Cache total size infor and used size info
        mStorageCacheHelper
                .cacheTotalSizeAndTotalUsedSize(mStorageInfo.totalBytes, privateUsedBytes);
        for (int i = 0, size = mSecondaryUsers.size(); i < size; i++) {
            final AbstractPreferenceController controller = mSecondaryUsers.get(i);
            if (controller instanceof SecondaryUserController) {
                SecondaryUserController userController = (SecondaryUserController) controller;
        for (NonCurrentUserController userController : mNonCurrentUsers) {
            userController.setTotalSize(mStorageInfo.totalBytes);
        }
        }

        mPreferenceController.onLoadFinished(mAppsResult, mUserId);
        updateSecondaryUserControllers(mSecondaryUsers, mAppsResult);
        setSecondaryUsersVisible(true);
        updateNonCurrentUserControllers(mNonCurrentUsers, mAppsResult);
        setNonCurrentUsersVisible(true);
    }

    @Override
@@ -253,20 +253,18 @@ public class StorageCategoryFragment extends DashboardFragment
                null /* volume */, new StorageManagerVolumeProvider(sm), mIsWorkProfile);
        controllers.add(mPreferenceController);

        mSecondaryUsers = SecondaryUserController.getSecondaryUserControllers(context,
                mUserManager, mIsWorkProfile /* isWorkProfileOnly */);
        controllers.addAll(mSecondaryUsers);

        mNonCurrentUsers = mIsWorkProfile ? EMPTY_LIST :
                NonCurrentUserController.getNonCurrentUserControllers(context, mUserManager);
        controllers.addAll(mNonCurrentUsers);
        return controllers;
    }

    /**
     * Updates the secondary user controller sizes.
     * Updates the non-current user controller sizes.
     */
    private void updateSecondaryUserControllers(List<AbstractPreferenceController> controllers,
    private void updateNonCurrentUserControllers(List<NonCurrentUserController> controllers,
            SparseArray<StorageAsyncLoader.StorageResult> stats) {
        for (int i = 0, size = controllers.size(); i < size; i++) {
            final AbstractPreferenceController controller = controllers.get(i);
        for (AbstractPreferenceController controller : controllers) {
            if (controller instanceof StorageAsyncLoader.ResultHandler) {
                StorageAsyncLoader.ResultHandler userController =
                        (StorageAsyncLoader.ResultHandler) controller;
@@ -296,6 +294,15 @@ public class StorageCategoryFragment extends DashboardFragment
    public void onLoaderReset(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader) {
    }

    @Override
    public void displayResourceTilesToScreen(PreferenceScreen screen) {
        final PreferenceGroup group = screen.findPreference(TARGET_PREFERENCE_GROUP_KEY);
        if (mNonCurrentUsers.isEmpty()) {
            screen.removePreference(group);
        }
        super.displayResourceTilesToScreen(screen);
    }

    @VisibleForTesting
    public PrivateStorageInfo getPrivateStorageInfo() {
        return mStorageInfo;
@@ -335,13 +342,9 @@ public class StorageCategoryFragment extends DashboardFragment
                        .isQuotaSupported(mSelectedStorageEntry.getFsUuid());
    }

    private void setSecondaryUsersVisible(boolean visible) {
        final Optional<SecondaryUserController> secondaryUserController = mSecondaryUsers.stream()
                .filter(controller -> controller instanceof SecondaryUserController)
                .map(controller -> (SecondaryUserController) controller)
                .findAny();
        if (secondaryUserController.isPresent()) {
            secondaryUserController.get().setPreferenceGroupVisible(visible);
    private void setNonCurrentUsersVisible(boolean visible) {
        if (!mNonCurrentUsers.isEmpty()) {
            mNonCurrentUsers.get(0).setPreferenceGroupVisible(visible);
        }
    }

@@ -361,7 +364,7 @@ public class StorageCategoryFragment extends DashboardFragment
        @Override
        public void onLoadFinished(
                Loader<SparseArray<Drawable>> loader, SparseArray<Drawable> data) {
            mSecondaryUsers
            mNonCurrentUsers
                    .stream()
                    .filter(controller -> controller instanceof UserIconLoader.UserIconHandler)
                    .forEach(
+36 −34
Original line number Diff line number Diff line
@@ -32,11 +32,12 @@ import android.os.storage.VolumeRecord;
import android.provider.SearchIndexableResource;
import android.text.TextUtils;
import android.util.SparseArray;
import android.view.View;

import androidx.annotation.VisibleForTesting;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;

import com.android.settings.R;
import com.android.settings.Utils;
@@ -44,7 +45,7 @@ import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.deviceinfo.storage.AutomaticStorageManagementSwitchPreferenceController;
import com.android.settings.deviceinfo.storage.DiskInitFragment;
import com.android.settings.deviceinfo.storage.ManageStoragePreferenceController;
import com.android.settings.deviceinfo.storage.SecondaryUserController;
import com.android.settings.deviceinfo.storage.NonCurrentUserController;
import com.android.settings.deviceinfo.storage.StorageAsyncLoader;
import com.android.settings.deviceinfo.storage.StorageCacheHelper;
import com.android.settings.deviceinfo.storage.StorageEntry;
@@ -64,7 +65,6 @@ import com.android.settingslib.search.SearchIndexable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

/**
 * Storage Settings main UI is composed by 3 fragments:
@@ -86,6 +86,7 @@ public class StorageDashboardFragment extends DashboardFragment
    private static final String TAG = "StorageDashboardFrag";
    private static final String SUMMARY_PREF_KEY = "storage_summary";
    private static final String SELECTED_STORAGE_ENTRY_KEY = "selected_storage_entry_key";
    private static final String TARGET_PREFERENCE_GROUP_KEY = "pref_non_current_users";
    private static final int STORAGE_JOB_ID = 0;
    private static final int ICON_JOB_ID = 1;
    private static final int VOLUME_SIZE_JOB_ID = 2;
@@ -101,7 +102,7 @@ public class StorageDashboardFragment extends DashboardFragment
    private VolumeOptionMenuController mOptionMenuController;
    private StorageSelectionPreferenceController mStorageSelectionController;
    private StorageUsageProgressBarPreferenceController mStorageUsageProgressBarController;
    private List<AbstractPreferenceController> mSecondaryUsers;
    private List<NonCurrentUserController> mNonCurrentUsers;
    private boolean mIsWorkProfile;
    private int mUserId;
    private boolean mIsLoadedFromCache;
@@ -232,9 +233,9 @@ public class StorageDashboardFragment extends DashboardFragment
        mOptionMenuController.setSelectedStorageEntry(mSelectedStorageEntry);
        getActivity().invalidateOptionsMenu();

        // To prevent flicker, hides secondary users preference.
        // To prevent flicker, hides non-current users preference.
        // onReceivedSizes will set it visible for private storage.
        setSecondaryUsersVisible(false);
        setNonCurrentUsersVisible(false);

        if (!mSelectedStorageEntry.isMounted()) {
            // Set null volume to hide category stats.
@@ -254,7 +255,7 @@ public class StorageDashboardFragment extends DashboardFragment
            mAppsResult = null;
            // Hide the loading spinner if there is cached data.
            if (mStorageCacheHelper.hasCachedSizeInfo()) {
                //TODO(b/220259287): apply cache mechanism to secondary user
                //TODO(b/220259287): apply cache mechanism to non-current user
                mPreferenceController.onLoadFinished(mAppsResult, mUserId);
            } else {
                maybeSetLoading(isQuotaSupported());
@@ -297,8 +298,8 @@ public class StorageDashboardFragment extends DashboardFragment
            mStorageEntries.addAll(
                    StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
            refreshUi();
            updateSecondaryUserControllers(mSecondaryUsers, mAppsResult);
            setSecondaryUsersVisible(true);
            updateNonCurrentUserControllers(mNonCurrentUsers, mAppsResult);
            setNonCurrentUsersVisible(true);
        }
    }

@@ -393,17 +394,13 @@ public class StorageDashboardFragment extends DashboardFragment
        // Cache total size and used size
        mStorageCacheHelper
                .cacheTotalSizeAndTotalUsedSize(mStorageInfo.totalBytes, privateUsedBytes);
        for (int i = 0, size = mSecondaryUsers.size(); i < size; i++) {
            final AbstractPreferenceController controller = mSecondaryUsers.get(i);
            if (controller instanceof SecondaryUserController) {
                SecondaryUserController userController = (SecondaryUserController) controller;
        for (NonCurrentUserController userController : mNonCurrentUsers) {
            userController.setTotalSize(mStorageInfo.totalBytes);
        }
        }

        mPreferenceController.onLoadFinished(mAppsResult, mUserId);
        updateSecondaryUserControllers(mSecondaryUsers, mAppsResult);
        setSecondaryUsersVisible(true);
        updateNonCurrentUserControllers(mNonCurrentUsers, mAppsResult);
        setNonCurrentUsersVisible(true);
    }

    @Override
@@ -429,20 +426,19 @@ public class StorageDashboardFragment extends DashboardFragment
                null /* volume */, new StorageManagerVolumeProvider(sm), mIsWorkProfile);
        controllers.add(mPreferenceController);

        mSecondaryUsers = SecondaryUserController.getSecondaryUserControllers(context,
                mUserManager, mIsWorkProfile /* isWorkProfileOnly */);
        controllers.addAll(mSecondaryUsers);
        mNonCurrentUsers = NonCurrentUserController.getNonCurrentUserControllers(context,
                mUserManager);
        controllers.addAll(mNonCurrentUsers);

        return controllers;
    }

    /**
     * Updates the secondary user controller sizes.
     * Updates the non-current user controller sizes.
     */
    private void updateSecondaryUserControllers(List<AbstractPreferenceController> controllers,
    private void updateNonCurrentUserControllers(List<NonCurrentUserController> controllers,
            SparseArray<StorageAsyncLoader.StorageResult> stats) {
        for (int i = 0, size = controllers.size(); i < size; i++) {
            final AbstractPreferenceController controller = controllers.get(i);
        for (AbstractPreferenceController controller : controllers) {
            if (controller instanceof StorageAsyncLoader.ResultHandler) {
                StorageAsyncLoader.ResultHandler userController =
                        (StorageAsyncLoader.ResultHandler) controller;
@@ -473,8 +469,8 @@ public class StorageDashboardFragment extends DashboardFragment
                    controllers.add(new StorageItemPreferenceController(context, null /* host */,
                            null /* volume */, new StorageManagerVolumeProvider(sm),
                            false /* isWorkProfile */));
                    controllers.addAll(SecondaryUserController.getSecondaryUserControllers(
                            context, userManager, false /* isWorkProfileOnly */));
                    controllers.addAll(NonCurrentUserController.getNonCurrentUserControllers(
                            context, userManager));
                    return controllers;
                }

@@ -501,6 +497,16 @@ public class StorageDashboardFragment extends DashboardFragment
    public void onLoaderReset(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader) {
    }


    @Override
    public void displayResourceTilesToScreen(PreferenceScreen screen) {
        final PreferenceGroup group = screen.findPreference(TARGET_PREFERENCE_GROUP_KEY);
        if (mNonCurrentUsers.isEmpty()) {
            screen.removePreference(group);
        }
        super.displayResourceTilesToScreen(screen);
    }

    @VisibleForTesting
    public PrivateStorageInfo getPrivateStorageInfo() {
        return mStorageInfo;
@@ -540,13 +546,9 @@ public class StorageDashboardFragment extends DashboardFragment
                        .isQuotaSupported(mSelectedStorageEntry.getFsUuid());
    }

    private void setSecondaryUsersVisible(boolean visible) {
        final Optional<SecondaryUserController> secondaryUserController = mSecondaryUsers.stream()
                .filter(controller -> controller instanceof SecondaryUserController)
                .map(controller -> (SecondaryUserController) controller)
                .findAny();
        if (secondaryUserController.isPresent()) {
            secondaryUserController.get().setPreferenceGroupVisible(visible);
    private void setNonCurrentUsersVisible(boolean visible) {
        if (!mNonCurrentUsers.isEmpty()) {
            mNonCurrentUsers.get(0).setPreferenceGroupVisible(visible);
        }
    }

@@ -566,7 +568,7 @@ public class StorageDashboardFragment extends DashboardFragment
        @Override
        public void onLoadFinished(
                Loader<SparseArray<Drawable>> loader, SparseArray<Drawable> data) {
            mSecondaryUsers
            mNonCurrentUsers
                    .stream()
                    .filter(controller -> controller instanceof UserIconLoader.UserIconHandler)
                    .forEach(
+47 −69
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.settings.deviceinfo.storage;

import android.app.ActivityManager;
import android.content.Context;
import android.content.pm.UserInfo;
import android.graphics.drawable.Drawable;
@@ -28,7 +29,6 @@ import androidx.annotation.VisibleForTesting;
import androidx.preference.PreferenceGroup;
import androidx.preference.PreferenceScreen;

import com.android.settings.Utils;
import com.android.settings.core.PreferenceControllerMixin;
import com.android.settings.deviceinfo.StorageItemPreference;
import com.android.settingslib.core.AbstractPreferenceController;
@@ -37,14 +37,14 @@ import java.util.ArrayList;
import java.util.List;

/**
 * SecondaryUserController controls the preferences on the Storage screen which had to do with
 * secondary users.
 * NonCurrentUserController controls the preferences on the Storage screen which had to do with
 * other users.
 */
public class SecondaryUserController extends AbstractPreferenceController implements
public class NonCurrentUserController extends AbstractPreferenceController implements
        PreferenceControllerMixin, StorageAsyncLoader.ResultHandler,
        UserIconLoader.UserIconHandler {
    // PreferenceGroupKey to try to add our preference onto.
    private static final String TARGET_PREFERENCE_GROUP_KEY = "pref_secondary_users";
    private static final String TARGET_PREFERENCE_GROUP_KEY = "pref_non_current_users";
    private static final String PREFERENCE_KEY_BASE = "pref_user_";
    private static final int SIZE_NOT_SET = -1;

@@ -57,60 +57,60 @@ public class SecondaryUserController extends AbstractPreferenceController implem
    private long mSize;
    private long mTotalSizeBytes;
    private boolean mIsVisible;
    private int[] mProfiles;
    private StorageCacheHelper mStorageCacheHelper;

    /**
     * Adds the appropriate controllers to a controller list for handling all secondary users on
     * a device.
     * Adds the appropriate controllers to a controller list for handling all full non current
     * users on a device.
     *
     * @param context           Context for initializing the preference controllers.
     * @param userManager       UserManagerWrapper for figuring out which controllers to add.
     * @param isWorkProfileOnly only shows secondary users of work profile.
     *                          (e.g., it should be true in work profile tab)
     */
    public static List<AbstractPreferenceController> getSecondaryUserControllers(
            Context context, UserManager userManager, boolean isWorkProfileOnly) {

        List<AbstractPreferenceController> controllers = new ArrayList<>();
        UserInfo primaryUser = userManager.getPrimaryUser();
        boolean addedUser = false;
    public static List<NonCurrentUserController> getNonCurrentUserControllers(
            Context context, UserManager userManager) {
        int currentUserId = ActivityManager.getCurrentUser();
        List<NonCurrentUserController> controllers = new ArrayList<>();
        List<UserInfo> infos = userManager.getUsers();
        for (int i = 0, size = infos.size(); i < size; i++) {
            UserInfo info = infos.get(i);
            if (info.isPrimary()) {
                continue;
            }

            if (Utils.isProfileOf(primaryUser, info)) {
        for (UserInfo info : infos) {
            if (info.id == currentUserId || info.isProfile()) {
                continue;
            }

            if (isWorkProfileOnly && !info.isManagedProfile()) {
                continue;
            int[] profiles = userManager.getProfileIds(info.id, false /* enabledOnly */);
            controllers.add(new NonCurrentUserController(context, info, profiles));
        }

            controllers.add(new SecondaryUserController(context, info));
            addedUser = true;
        return controllers;
    }

        if (!addedUser) {
            controllers.add(new NoSecondaryUserController(context));
        }
        return controllers;
    /**
     * Constructor for a given non-current user.
     *
     * @param context Context to initialize the underlying {@link AbstractPreferenceController}.
     * @param info    {@link UserInfo} for the non-current user which these controllers cover.
     * @param profiles list of IDs or user and its profiles
     */
    @VisibleForTesting
    NonCurrentUserController(Context context, @NonNull UserInfo info, @NonNull int[] profiles) {
        super(context);
        mUser = info;
        mSize = SIZE_NOT_SET;
        mStorageCacheHelper = new StorageCacheHelper(context, info.id);
        mProfiles = profiles;
    }

    /**
     * Constructor for a given secondary user.
     * Constructor for a given non-current user.
     *
     * @param context Context to initialize the underlying {@link AbstractPreferenceController}.
     * @param info    {@link UserInfo} for the secondary user which this controllers covers.
     * @param info    {@link UserInfo} for the non-current user which these controllers cover.
     */
    @VisibleForTesting
    SecondaryUserController(Context context, @NonNull UserInfo info) {
    NonCurrentUserController(Context context, @NonNull UserInfo info) {
        super(context);
        mUser = info;
        mSize = SIZE_NOT_SET;
        mStorageCacheHelper = new StorageCacheHelper(context, info.id);
        mProfiles = new int[]{info.id};
    }

    @Override
@@ -140,7 +140,7 @@ public class SecondaryUserController extends AbstractPreferenceController implem
    }

    /**
     * Returns the user for which this is the secondary user controller.
     * Returns the user for which this is the non-current user controller.
     */
    @NonNull
    public UserInfo getUser() {
@@ -169,7 +169,7 @@ public class SecondaryUserController extends AbstractPreferenceController implem
    }

    /**
     * Sets visibility of the PreferenceGroup of secondary user.
     * Sets visibility of the PreferenceGroup of non-current user.
     *
     * @param visible Visibility of the PreferenceGroup.
     */
@@ -187,10 +187,15 @@ public class SecondaryUserController extends AbstractPreferenceController implem
            return;
        }
        final StorageAsyncLoader.StorageResult result = stats.get(getUser().id);

        if (result != null) {
            setSize(result.externalStats.totalBytes, true /* animate */);
            long totalSize = 0;
            for (int id : mProfiles) {
                totalSize += stats.get(id).externalStats.totalBytes;
            }
            setSize(totalSize, true /* animate */);
            // TODO(b/171758224): Update the source of size info
            mStorageCacheHelper.cacheUsedSize(result.externalStats.totalBytes);
            mStorageCacheHelper.cacheUsedSize(totalSize);
        }
    }

@@ -205,31 +210,4 @@ public class SecondaryUserController extends AbstractPreferenceController implem
            mStoragePreference.setIcon(mUserIcon);
        }
    }

    @VisibleForTesting
    static class NoSecondaryUserController extends AbstractPreferenceController implements
            PreferenceControllerMixin {
        public NoSecondaryUserController(Context context) {
            super(context);
        }

        @Override
        public void displayPreference(PreferenceScreen screen) {
            final PreferenceGroup group = screen.findPreference(TARGET_PREFERENCE_GROUP_KEY);
            if (group == null) {
                return;
            }
            screen.removePreference(group);
        }

        @Override
        public boolean isAvailable() {
            return true;
        }

        @Override
        public String getPreferenceKey() {
            return null;
        }
    }
}
Loading