Add support for user profiles to the Storage Settings.

This adds new preferences for each profile (such as the work
profile) and defines a new view for viewing the storage
breakdown for the individual profile. The functionality closely
mimics the presentation on the main view, but without the system-wide
breakdown and without any additional users/profiles.

Bug: 34715777
Test: Settings Robotests

Change-Id: I19d449b648c6566331fd02e45c2e45f8c74ea7e7
This commit is contained in:
Daniel Nishi
2017-02-15 15:25:48 -08:00
parent 182e6ce2c7
commit 9f60f42a94
15 changed files with 553 additions and 62 deletions

View File

@@ -65,6 +65,7 @@ import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.preference.PreferenceFrameLayout;
import android.provider.ContactsContract.CommonDataKinds;
import android.provider.ContactsContract.Contacts;
@@ -1258,4 +1259,21 @@ public final class Utils extends com.android.settingslib.Utils {
(user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID
&& user.profileGroupId == profile.profileGroupId);
}
/**
* Tries to initalize a volume with the given bundle. If it is a valid, private, and readable
* {@link VolumeInfo}, it is returned. If it is not valid, null is returned.
*/
@Nullable
public static VolumeInfo maybeInitializeVolume(StorageManager sm, Bundle bundle) {
final String volumeId = bundle.getString(VolumeInfo.EXTRA_VOLUME_ID,
VolumeInfo.ID_PRIVATE_INTERNAL);
VolumeInfo volume = sm.findVolumeById(volumeId);
return isVolumeValid(volume) ? volume : null;
}
private static boolean isVolumeValid(VolumeInfo volume) {
return (volume != null) && (volume.getType() == VolumeInfo.TYPE_PRIVATE)
&& volume.isMountedReadable();
}
}

View File

@@ -25,11 +25,11 @@ import android.os.UserManager;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.provider.SearchIndexableResource;
import android.support.annotation.VisibleForTesting;
import android.util.SparseArray;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.PackageManagerWrapperImpl;
import com.android.settings.applications.UserManagerWrapper;
import com.android.settings.applications.UserManagerWrapperImpl;
@@ -48,7 +48,6 @@ import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
public class StorageDashboardFragment extends DashboardFragment
implements LoaderManager.LoaderCallbacks<SparseArray<StorageAsyncLoader.AppsStorageResult>> {
@@ -61,11 +60,6 @@ public class StorageDashboardFragment extends DashboardFragment
private StorageItemPreferenceController mPreferenceController;
private List<PreferenceController> mSecondaryUsers;
private boolean isVolumeValid() {
return (mVolume != null) && (mVolume.getType() == VolumeInfo.TYPE_PRIVATE)
&& mVolume.isMountedReadable();
}
@Override
public void onResume() {
super.onResume();
@@ -101,7 +95,8 @@ public class StorageDashboardFragment extends DashboardFragment
// Initialize the storage sizes that we can quickly calc.
final Context context = getActivity();
StorageManager sm = context.getSystemService(StorageManager.class);
if (!initializeVolume(sm, getArguments())) {
mVolume = Utils.maybeInitializeVolume(sm, getArguments());
if (mVolume == null) {
getActivity().finish();
return;
}
@@ -156,17 +151,6 @@ public class StorageDashboardFragment extends DashboardFragment
return controllers;
}
/**
* Initializes the volume with a given bundle and returns if the volume is valid.
*/
@VisibleForTesting
boolean initializeVolume(StorageManager sm, Bundle bundle) {
String volumeId = bundle.getString(VolumeInfo.EXTRA_VOLUME_ID,
VolumeInfo.ID_PRIVATE_INTERNAL);
mVolume = sm.findVolumeById(volumeId);
return isVolumeValid();
}
/**
* Updates the secondary user controller sizes.
*/
@@ -174,13 +158,10 @@ public class StorageDashboardFragment extends DashboardFragment
SparseArray<StorageAsyncLoader.AppsStorageResult> stats) {
for (int i = 0, size = controllers.size(); i < size; i++) {
PreferenceController controller = controllers.get(i);
if (controller instanceof SecondaryUserController) {
SecondaryUserController userController = (SecondaryUserController) controller;
int userId = userController.getUser().id;
StorageAsyncLoader.AppsStorageResult result = stats.get(userId);
if (result != null) {
userController.setSize(result.externalStats.totalBytes);
}
if (controller instanceof StorageAsyncLoader.ResultHandler) {
StorageAsyncLoader.ResultHandler userController =
(StorageAsyncLoader.ResultHandler) controller;
userController.handleResult(stats);
}
}
}

View File

@@ -0,0 +1,128 @@
/*
* Copyright (C) 2017 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.settings.deviceinfo;
import android.app.LoaderManager;
import android.content.Context;
import android.content.Loader;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.util.SparseArray;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.Utils;
import com.android.settings.applications.PackageManagerWrapperImpl;
import com.android.settings.applications.UserManagerWrapperImpl;
import com.android.settings.core.PreferenceController;
import com.android.settings.dashboard.DashboardFragment;
import com.android.settings.deviceinfo.storage.StorageAsyncLoader;
import com.android.settings.deviceinfo.storage.StorageAsyncLoader.AppsStorageResult;
import com.android.settings.deviceinfo.storage.StorageItemPreferenceController;
import com.android.settingslib.applications.StorageStatsSource;
import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
import java.util.ArrayList;
import java.util.List;
/**
* StorageProfileFragment is a fragment which shows the storage results for a profile of the
* primary user.
*/
public class StorageProfileFragment extends DashboardFragment
implements LoaderManager.LoaderCallbacks<SparseArray<AppsStorageResult>> {
private static final String TAG = "StorageProfileFragment";
public static final String USER_ID_EXTRA = "userId";
private static final int APPS_JOB_ID = 0;
private VolumeInfo mVolume;
private int mUserId;
private StorageItemPreferenceController mPreferenceController;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
final Bundle args = getArguments();
// Initialize the storage sizes that we can quickly calc.
final Context context = getActivity();
final StorageManager sm = context.getSystemService(StorageManager.class);
mVolume = Utils.maybeInitializeVolume(sm, args);
if (mVolume == null) {
getActivity().finish();
return;
}
mPreferenceController.setVolume(mVolume);
mUserId = args.getInt(USER_ID_EXTRA, UserHandle.myUserId());
mPreferenceController.setUserId(mUserId);
}
@Override
public void onResume() {
super.onResume();
getLoaderManager().initLoader(APPS_JOB_ID, Bundle.EMPTY, this);
}
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.SETTINGS_STORAGE_PROFILE;
}
@Override
protected String getLogTag() {
return TAG;
}
@Override
protected int getPreferenceScreenResId() {
return R.xml.storage_profile_fragment;
}
@Override
protected List<PreferenceController> getPreferenceControllers(Context context) {
final List<PreferenceController> controllers = new ArrayList<>();
final StorageManager sm = context.getSystemService(StorageManager.class);
mPreferenceController = new StorageItemPreferenceController(context, this,
mVolume, new StorageManagerVolumeProvider(sm));
controllers.add(mPreferenceController);
return controllers;
}
@Override
public Loader<SparseArray<AppsStorageResult>> onCreateLoader(int id, Bundle args) {
Context context = getContext();
return new StorageAsyncLoader(context,
new UserManagerWrapperImpl(context.getSystemService(UserManager.class)),
mVolume.fsUuid,
new StorageStatsSource(context),
new PackageManagerWrapperImpl(context.getPackageManager()));
}
@Override
public void onLoadFinished(Loader<SparseArray<AppsStorageResult>> loader,
SparseArray<AppsStorageResult> result) {
mPreferenceController.onLoadFinished(result.get(mUserId));
}
@Override
public void onLoaderReset(Loader<SparseArray<AppsStorageResult>> loader) {
}
}

View File

@@ -23,6 +23,7 @@ import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceScreen;
import android.util.SparseArray;
import com.android.settings.Utils;
import com.android.settings.applications.UserManagerWrapper;
@@ -35,10 +36,12 @@ import java.util.List;
* SecondaryUserController controls the preferences on the Storage screen which had to do with
* secondary users.
*/
public class SecondaryUserController extends PreferenceController {
public class SecondaryUserController extends PreferenceController implements
StorageAsyncLoader.ResultHandler {
// PreferenceGroupKey to try to add our preference onto.
private static final String TARGET_PREFERENCE_GROUP_KEY = "pref_secondary_users";
private static final String PREFERENCE_KEY_BASE = "pref_user_";
private static final int USER_PROFILE_INSERTION_LOCATION = 6;
private static final int SIZE_NOT_SET = -1;
private @NonNull UserInfo mUser;
@@ -59,7 +62,13 @@ public class SecondaryUserController extends PreferenceController {
List<UserInfo> infos = userManager.getUsers();
for (int i = 0, size = infos.size(); i < size; i++) {
UserInfo info = infos.get(i);
if (info.equals(primaryUser)) {
continue;
}
if (info == null || Utils.isProfileOf(primaryUser, info)) {
controllers.add(new UserProfileController(context, info,
USER_PROFILE_INSERTION_LOCATION));
continue;
}
@@ -131,6 +140,14 @@ public class SecondaryUserController extends PreferenceController {
}
}
public void handleResult(SparseArray<StorageAsyncLoader.AppsStorageResult> stats) {
int userId = getUser().id;
StorageAsyncLoader.AppsStorageResult result = stats.get(userId);
if (result != null) {
setSize(result.externalStats.totalBytes);
}
}
private static class NoSecondaryUserController extends PreferenceController {
public NoSecondaryUserController(Context context) {
super(context);

View File

@@ -23,10 +23,9 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.SparseArray;
import android.util.Log;
import android.util.SparseArray;
import com.android.settings.applications.PackageManagerWrapper;
import com.android.settings.applications.UserManagerWrapper;
@@ -34,7 +33,6 @@ import com.android.settings.utils.AsyncLoader;
import com.android.settingslib.applications.StorageStatsSource;
import java.util.List;
import java.util.Map;
/**
* StorageAsyncLoader is a Loader which loads categorized app information and external stats for all
@@ -118,4 +116,12 @@ public class StorageAsyncLoader
public long otherAppsSize;
public StorageStatsSource.ExternalStorageStats externalStats;
}
/**
* ResultHandler defines a destination of data which can handle a result from
* {@link StorageAsyncLoader}.
*/
public interface ResultHandler {
void handleResult(SparseArray<AppsStorageResult> result);
}
}

View File

@@ -22,7 +22,6 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.VolumeInfo;
import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
@@ -36,12 +35,12 @@ import com.android.settings.Utils;
import com.android.settings.applications.ManageApplications;
import com.android.settings.core.PreferenceController;
import com.android.settings.core.instrumentation.MetricsFeatureProvider;
import com.android.settings.overlay.FeatureFactory;
import com.android.settingslib.deviceinfo.StorageMeasurement;
import com.android.settingslib.deviceinfo.StorageVolumeProvider;
import com.android.settingslib.applications.StorageStatsSource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
@@ -70,7 +69,7 @@ public class StorageItemPreferenceController extends PreferenceController {
private final MetricsFeatureProvider mMetricsFeatureProvider;
private final StorageVolumeProvider mSvp;
private VolumeInfo mVolume;
private final int mUserId;
private int mUserId;
private long mSystemSize;
private StorageItemPreferenceAlternate mPhotoPreference;
@@ -89,8 +88,7 @@ public class StorageItemPreferenceController extends PreferenceController {
mVolume = volume;
mSvp = svp;
mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider();
UserManager um = mContext.getSystemService(UserManager.class);
mUserId = um.getUserHandle();
mUserId = UserHandle.myUserId();
}
@Override
@@ -157,6 +155,13 @@ public class StorageItemPreferenceController extends PreferenceController {
mVolume = volume;
}
/**
* Sets the user id for which this preference controller is handling.
*/
public void setUserId(int userId) {
mUserId = userId;
}
@Override
public void displayPreference(PreferenceScreen screen) {
mPhotoPreference = (StorageItemPreferenceAlternate) screen.findPreference(PHOTO_KEY);
@@ -173,7 +178,9 @@ public class StorageItemPreferenceController extends PreferenceController {
mAudioPreference.setStorageSize(data.musicAppsSize + data.externalStats.audioBytes);
mGamePreference.setStorageSize(data.gamesSize);
mAppPreference.setStorageSize(data.otherAppsSize);
mSystemPreference.setStorageSize(mSystemSize);
if (mSystemPreference != null) {
mSystemPreference.setStorageSize(mSystemSize);
}
long unattributedBytes = data.externalStats.totalBytes - data.externalStats.audioBytes
- data.externalStats.videoBytes - data.externalStats.imageBytes;
@@ -188,6 +195,20 @@ public class StorageItemPreferenceController extends PreferenceController {
mSystemSize = systemSize;
}
/**
* Returns a list of keys used by this preference controller.
*/
public static List<String> getUsedKeys() {
List<String> list = new ArrayList<>();
list.add(PHOTO_KEY);
list.add(AUDIO_KEY);
list.add(GAME_KEY);
list.add(OTHER_APPS_KEY);
list.add(SYSTEM_KEY);
list.add(FILES_KEY);
return list;
}
private Intent getPhotosIntent() {
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);

View File

@@ -0,0 +1,106 @@
/*
* Copyright (C) 2017 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.settings.deviceinfo.storage;
import android.content.Context;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.Bundle;
import android.os.storage.VolumeInfo;
import android.support.v7.preference.Preference;
import android.support.v7.preference.PreferenceScreen;
import android.util.SparseArray;
import com.android.internal.logging.nano.MetricsProto;
import com.android.internal.util.Preconditions;
import com.android.settings.Utils;
import com.android.settings.core.PreferenceController;
import com.android.settings.deviceinfo.StorageProfileFragment;
import com.android.settingslib.drawer.SettingsDrawerActivity;
/**
* Defines a {@link PreferenceController} which handles a single profile of the primary user.
*/
public class UserProfileController extends PreferenceController implements
StorageAsyncLoader.ResultHandler {
private static final String PREFERENCE_KEY_BASE = "pref_profile_";
private StorageItemPreferenceAlternate mStoragePreference;
private UserInfo mUser;
private final int mPreferenceOrder;
public UserProfileController(Context context, UserInfo info, int preferenceOrder) {
super(context);
mUser = Preconditions.checkNotNull(info);
mPreferenceOrder = preferenceOrder;
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return PREFERENCE_KEY_BASE + mUser.id;
}
@Override
public void displayPreference(PreferenceScreen screen) {
mStoragePreference = new StorageItemPreferenceAlternate(mContext);
mStoragePreference.setOrder(mPreferenceOrder);
mStoragePreference.setKey(PREFERENCE_KEY_BASE + mUser.id);
mStoragePreference.setTitle(mUser.name);
screen.addPreference(mStoragePreference);
}
@Override
public boolean handlePreferenceTreeClick(Preference preference) {
if (preference != null && mStoragePreference == preference) {
Bundle args = new Bundle(2);
args.putInt(StorageProfileFragment.USER_ID_EXTRA, mUser.id);
args.putString(VolumeInfo.EXTRA_VOLUME_ID, VolumeInfo.ID_PRIVATE_INTERNAL);
Intent intent = Utils.onBuildStartFragmentIntent(mContext,
StorageProfileFragment.class.getName(), args, null, 0,
mUser.name, false, MetricsProto.MetricsEvent.DEVICEINFO_STORAGE);
intent.putExtra(SettingsDrawerActivity.EXTRA_SHOW_MENU, true);
mContext.startActivity(intent);
return true;
}
return false;
}
@Override
public void handleResult(SparseArray<StorageAsyncLoader.AppsStorageResult> stats) {
Preconditions.checkNotNull(stats);
int userId = mUser.id;
StorageAsyncLoader.AppsStorageResult result = stats.get(userId);
if (result != null) {
setSize(result.externalStats.totalBytes);
}
}
/**
* Sets the size for the preference using a byte count.
*/
public void setSize(long size) {
if (mStoragePreference != null) {
mStoragePreference.setStorageSize(size);
}
}
}

View File

@@ -45,6 +45,7 @@ import com.android.settings.connecteddevice.ConnectedDeviceDashboardFragment;
import com.android.settings.datausage.DataUsageMeteredSettings;
import com.android.settings.datausage.DataUsageSummary;
import com.android.settings.deviceinfo.StorageDashboardFragment;
import com.android.settings.deviceinfo.StorageProfileFragment;
import com.android.settings.deviceinfo.StorageSettings;
import com.android.settings.display.ScreenZoomSettings;
import com.android.settings.enterprise.EnterprisePrivacySettings;