From dfed8a2acb62fce32eb6cf6e1665b98696cdd992 Mon Sep 17 00:00:00 2001 From: Daniel Nishi Date: Thu, 19 Jan 2017 16:32:41 -0800 Subject: [PATCH 1/2] Add support for visualizing secondary users. This functionality adds the secondary users to the screen, but currently does not populate the information for them. Once the external stats query works, I will add a loader which will populate this information. This also does not cover work profiles. Support for that is forthcoming. Bug: 34715777, 34225103 Test: Settings Robotest Change-Id: Ib9b692b214f5ce5d303dfd64516381443d4acebd --- res/xml/storage_dashboard_fragment.xml | 3 + src/com/android/settings/Utils.java | 12 ++ .../applications/UserManagerWrapper.java | 32 ++++ .../applications/UserManagerWrapperImpl.java | 40 +++++ .../deviceinfo/PrivateVolumeSettings.java | 10 +- .../deviceinfo/StorageDashboardFragment.java | 8 + .../storage/SecondaryUserController.java | 141 +++++++++++++++++ .../StorageItemPreferenceController.java | 3 + .../storage/SecondaryUserControllerTest.java | 149 ++++++++++++++++++ 9 files changed, 390 insertions(+), 8 deletions(-) create mode 100644 src/com/android/settings/applications/UserManagerWrapper.java create mode 100644 src/com/android/settings/applications/UserManagerWrapperImpl.java create mode 100644 src/com/android/settings/deviceinfo/storage/SecondaryUserController.java create mode 100644 tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java diff --git a/res/xml/storage_dashboard_fragment.xml b/res/xml/storage_dashboard_fragment.xml index e08ca961fd9..271d31bd859 100644 --- a/res/xml/storage_dashboard_fragment.xml +++ b/res/xml/storage_dashboard_fragment.xml @@ -43,6 +43,9 @@ android:key="pref_system" android:title="@string/storage_detail_system"> + getUsers(); +} diff --git a/src/com/android/settings/applications/UserManagerWrapperImpl.java b/src/com/android/settings/applications/UserManagerWrapperImpl.java new file mode 100644 index 00000000000..14ea64ae345 --- /dev/null +++ b/src/com/android/settings/applications/UserManagerWrapperImpl.java @@ -0,0 +1,40 @@ +/* + * 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.applications; + +import android.content.pm.UserInfo; +import android.os.UserManager; + +import java.util.List; + +public class UserManagerWrapperImpl implements UserManagerWrapper { + private UserManager mUserManager; + + public UserManagerWrapperImpl(UserManager userManager) { + mUserManager = userManager; + } + + @Override + public UserInfo getPrimaryUser() { + return mUserManager.getPrimaryUser(); + } + + @Override + public List getUsers() { + return mUserManager.getUsers(); + } +} diff --git a/src/com/android/settings/deviceinfo/PrivateVolumeSettings.java b/src/com/android/settings/deviceinfo/PrivateVolumeSettings.java index 6c3b9e62450..eb07a7fd4af 100644 --- a/src/com/android/settings/deviceinfo/PrivateVolumeSettings.java +++ b/src/com/android/settings/deviceinfo/PrivateVolumeSettings.java @@ -228,7 +228,7 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment { // Add current user and its profiles first for (int userIndex = 0; userIndex < userCount; ++userIndex) { final UserInfo userInfo = allUsers.get(userIndex); - if (isProfileOf(mCurrentUser, userInfo)) { + if (Utils.isProfileOf(mCurrentUser, userInfo)) { final PreferenceGroup details = showHeaders ? addCategory(screen, userInfo.name) : screen; addDetailItems(details, showShared, userInfo.id); @@ -242,7 +242,7 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment { getText(R.string.storage_other_users)); for (int userIndex = 0; userIndex < userCount; ++userIndex) { final UserInfo userInfo = allUsers.get(userIndex); - if (!isProfileOf(mCurrentUser, userInfo)) { + if (!Utils.isProfileOf(mCurrentUser, userInfo)) { addItem(otherUsers, /* titleRes */ 0, userInfo.name, userInfo.id); } } @@ -649,12 +649,6 @@ public class PrivateVolumeSettings extends SettingsPreferenceFragment { pref.setStorageSize(size, mTotalSize); } - private boolean isProfileOf(UserInfo user, UserInfo profile) { - return user.id == profile.id || - (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID - && user.profileGroupId == profile.profileGroupId); - } - private static long totalValues(MeasurementDetails details, int userId, String... keys) { long total = 0; HashMap map = details.mediaSize.get(userId); diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java index 914a8fe535a..0160534f013 100644 --- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java +++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java @@ -18,6 +18,7 @@ package com.android.settings.deviceinfo; import android.content.Context; import android.os.Bundle; +import android.os.UserManager; import android.os.storage.StorageManager; import android.os.storage.VolumeInfo; import android.provider.SearchIndexableResource; @@ -25,8 +26,11 @@ import android.support.annotation.VisibleForTesting; import com.android.internal.logging.nano.MetricsProto; import com.android.settings.R; +import com.android.settings.applications.UserManagerWrapper; +import com.android.settings.applications.UserManagerWrapperImpl; import com.android.settings.core.PreferenceController; import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.deviceinfo.storage.SecondaryUserController; import com.android.settings.deviceinfo.storage.StorageItemPreferenceController; import com.android.settings.deviceinfo.storage.StorageSummaryDonutPreferenceController; import com.android.settings.overlay.FeatureFactory; @@ -110,6 +114,10 @@ public class StorageDashboardFragment extends DashboardFragment { mPreferenceController = new StorageItemPreferenceController(context, this, mVolume, new StorageManagerVolumeProvider(sm)); controllers.add(mPreferenceController); + + UserManagerWrapper userManager = + new UserManagerWrapperImpl(context.getSystemService(UserManager.class)); + SecondaryUserController.addAllSecondaryUserControllers(context, userManager, controllers); controllers.add(new ManageStoragePreferenceController(context)); return controllers; } diff --git a/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java b/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java new file mode 100644 index 00000000000..b27dfa36058 --- /dev/null +++ b/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java @@ -0,0 +1,141 @@ +/* + * 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.pm.UserInfo; +import android.os.UserManager; +import android.support.annotation.VisibleForTesting; +import android.support.v7.preference.PreferenceGroup; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.Utils; +import com.android.settings.applications.UserManagerWrapper; +import com.android.settings.core.PreferenceController; + +import java.util.List; + +/** + * SecondaryUserController controls the preferences on the Storage screen which had to do with + * secondary users. + */ +public class SecondaryUserController extends PreferenceController { + // 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 UserInfo mUser; + private StorageItemPreferenceAlternate mPreference; + + /** + * Adds the appropriate controllers to a controller list for handling all secondary users on + * a device. + * @param context Context for initializing the preference controllers. + * @param controllers List of preference controllers for a Settings fragment. + */ + public static void addAllSecondaryUserControllers(Context context, + UserManagerWrapper userManager, List controllers) { + UserInfo primaryUser = userManager.getPrimaryUser(); + boolean addedUser = false; + List infos = userManager.getUsers(); + for (int i = 0, size = infos.size(); i < size; i++) { + UserInfo info = infos.get(i); + if (Utils.isProfileOf(primaryUser, info)) { + continue; + } + + controllers.add(new SecondaryUserController(context, info)); + addedUser = true; + } + + if (!addedUser) { + controllers.add(new NoSecondaryUserController(context)); + } + } + + /** + * Constructor for a given secondary user. + * @param context Context to initialize the underlying {@link PreferenceController}. + * @param info {@link UserInfo} for the secondary user which this controllers covers. + */ + @VisibleForTesting + SecondaryUserController(Context context, UserInfo info) { + super(context); + mUser = info; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + if (mPreference == null) { + mPreference = new StorageItemPreferenceAlternate(mContext); + + PreferenceGroup group = + (PreferenceGroup) screen.findPreference(TARGET_PREFERENCE_GROUP_KEY); + mPreference.setTitle(mUser.name); + mPreference.setKey(PREFERENCE_KEY_BASE + mUser.id); + group.setVisible(true); + group.addPreference(mPreference); + } + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + return (mPreference != null) ? mPreference.getKey() : null; + } + + /** + * Sets the size for the preference. + * @param size Size in bytes. + */ + public void setSize(long size) { + if (mPreference != null) { + mPreference.setStorageSize(size); + } + } + + private static class NoSecondaryUserController extends PreferenceController { + public NoSecondaryUserController(Context context) { + super(context); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + PreferenceGroup group = + (PreferenceGroup) 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; + } + + } +} diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java index 54a65dac7f2..7afd0618870 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java +++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java @@ -110,6 +110,9 @@ public class StorageItemPreferenceController extends PreferenceController // TODO: Currently, this reflects the existing behavior for these toggles. // After the intermediate views are built, swap them in. Intent intent = null; + if (preference.getKey() == null) { + return false; + } switch (preference.getKey()) { case PHOTO_KEY: intent = getPhotosIntent(); diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java new file mode 100644 index 00000000000..43942a63d5e --- /dev/null +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java @@ -0,0 +1,149 @@ +/* + * 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 static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.UserInfo; +import android.support.v7.preference.Preference; +import android.support.v7.preference.PreferenceGroup; +import android.support.v7.preference.PreferenceScreen; + +import com.android.settings.SettingsRobolectricTestRunner; +import com.android.settings.TestConfig; +import com.android.settings.applications.UserManagerWrapper; +import com.android.settings.core.PreferenceController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; + +@RunWith(SettingsRobolectricTestRunner.class) +@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) +public class SecondaryUserControllerTest { + private static final String TEST_NAME = "Fred"; + private static final String TARGET_PREFERENCE_GROUP_KEY = "pref_secondary_users"; + @Mock + private UserManagerWrapper mUserManager; + + private Context mContext; + private SecondaryUserController mController; + private UserInfo mPrimaryUser; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mPrimaryUser = new UserInfo(); + mController = new SecondaryUserController(mContext, mPrimaryUser); + } + + @Test + public void controllerAddsSecondaryUser() throws Exception { + mPrimaryUser.name = TEST_NAME; + PreferenceScreen screen = mock(PreferenceScreen.class); + PreferenceGroup group = mock(PreferenceGroup.class); + when(screen.findPreference(anyString())).thenReturn(group); + when(group.getKey()).thenReturn(TARGET_PREFERENCE_GROUP_KEY); + mController.displayPreference(screen); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Preference.class); + verify(group).addPreference(argumentCaptor.capture()); + Preference preference = argumentCaptor.getValue(); + assertThat(preference.getTitle()).isEqualTo(TEST_NAME); + } + + @Test + public void controllerUpdatesSummaryOfNewPreference() throws Exception { + mPrimaryUser.name = TEST_NAME; + PreferenceScreen screen = mock(PreferenceScreen.class); + PreferenceGroup group = mock(PreferenceGroup.class); + when(screen.findPreference(anyString())).thenReturn(group); + when(group.getKey()).thenReturn(TARGET_PREFERENCE_GROUP_KEY); + mController.displayPreference(screen); + mController.setSize(10L); + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Preference.class); + + verify(group).addPreference(argumentCaptor.capture()); + + Preference preference = argumentCaptor.getValue(); + assertThat(preference.getSummary()).isEqualTo("10.00B"); + } + + @Test + public void noSecondaryUserAddedIfNoneExist() throws Exception { + ArrayList userInfos = new ArrayList<>(); + userInfos.add(mPrimaryUser); + when(mUserManager.getPrimaryUser()).thenReturn(mPrimaryUser); + when(mUserManager.getUsers()).thenReturn(userInfos); + ArrayList controllers = new ArrayList<>(); + + SecondaryUserController.addAllSecondaryUserControllers(mContext, mUserManager, controllers); + + assertThat(controllers).hasSize(1); + // We should have the NoSecondaryUserController. + assertThat(controllers.get(0) instanceof SecondaryUserController).isFalse(); + } + + @Test + public void secondaryUserAddedIfHasDistinctId() throws Exception { + ArrayList userInfos = new ArrayList<>(); + UserInfo secondaryUser = new UserInfo(); + secondaryUser.id = 10; + secondaryUser.profileGroupId = 101010; // this just has to be something not 0 + userInfos.add(mPrimaryUser); + userInfos.add(secondaryUser); + when(mUserManager.getPrimaryUser()).thenReturn(mPrimaryUser); + when(mUserManager.getUsers()).thenReturn(userInfos); + ArrayList controllers = new ArrayList<>(); + + SecondaryUserController.addAllSecondaryUserControllers(mContext, mUserManager, controllers); + + assertThat(controllers).hasSize(1); + assertThat(controllers.get(0) instanceof SecondaryUserController).isTrue(); + } + + @Test + public void profilesOfPrimaryUserAreIgnored() throws Exception { + ArrayList userInfos = new ArrayList<>(); + UserInfo secondaryUser = new UserInfo(); + secondaryUser.id = mPrimaryUser.id; + userInfos.add(mPrimaryUser); + userInfos.add(secondaryUser); + when(mUserManager.getPrimaryUser()).thenReturn(mPrimaryUser); + when(mUserManager.getUsers()).thenReturn(userInfos); + ArrayList controllers = new ArrayList<>(); + + SecondaryUserController.addAllSecondaryUserControllers(mContext, mUserManager, controllers); + + assertThat(controllers).hasSize(1); + assertThat(controllers.get(0) instanceof SecondaryUserController).isFalse(); + } +} From 422e7c354dc57aea64dd5a455167f5f2a5cb5695 Mon Sep 17 00:00:00 2001 From: Daniel Nishi Date: Thu, 9 Feb 2017 16:07:22 -0800 Subject: [PATCH 2/2] Update the loading of info for the secondary users. Bug: 34715777, 34225103 Test: Settings Robotest Change-Id: I32bb15ad8bc866c1fd41728e56faa8b09ae11eb6 --- src/com/android/settings/Utils.java | 10 +-- .../applications/UserManagerWrapper.java | 1 - .../deviceinfo/StorageDashboardFragment.java | 60 ++++++++++++++++-- .../storage/SecondaryUserController.java | 51 ++++++++++----- .../storage/StorageAsyncLoader.java | 47 +++++++++----- .../StorageItemPreferenceAlternate.java | 2 - .../StorageItemPreferenceController.java | 27 ++------ .../storage/SecondaryUserControllerTest.java | 15 +++-- .../StorageItemPreferenceControllerTest.java | 3 +- .../storage/StorageAsyncLoaderTest.java | 63 +++++++++++++++---- 10 files changed, 192 insertions(+), 87 deletions(-) diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index d6b962d37b4..124c44192d3 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -16,6 +16,11 @@ package com.android.settings; +import static android.content.Intent.EXTRA_USER; +import static android.content.Intent.EXTRA_USER_ID; +import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; +import static android.text.format.DateUtils.FORMAT_SHOW_DATE; + import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AlertDialog; @@ -104,11 +109,6 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; -import static android.content.Intent.EXTRA_USER; -import static android.content.Intent.EXTRA_USER_ID; -import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH; -import static android.text.format.DateUtils.FORMAT_SHOW_DATE; - public final class Utils extends com.android.settingslib.Utils { private static final String TAG = "Settings"; diff --git a/src/com/android/settings/applications/UserManagerWrapper.java b/src/com/android/settings/applications/UserManagerWrapper.java index 5b4ed2aa05f..daefb844d91 100644 --- a/src/com/android/settings/applications/UserManagerWrapper.java +++ b/src/com/android/settings/applications/UserManagerWrapper.java @@ -17,7 +17,6 @@ package com.android.settings.applications; import android.content.pm.UserInfo; - import java.util.List; /** diff --git a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java index 0160534f013..d65eb750bf7 100644 --- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java +++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java @@ -16,40 +16,50 @@ 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.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.applications.PackageManagerWrapperImpl; import com.android.settings.applications.UserManagerWrapper; import com.android.settings.applications.UserManagerWrapperImpl; import com.android.settings.core.PreferenceController; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.deviceinfo.storage.SecondaryUserController; +import com.android.settings.deviceinfo.storage.StorageAsyncLoader; import com.android.settings.deviceinfo.storage.StorageItemPreferenceController; import com.android.settings.deviceinfo.storage.StorageSummaryDonutPreferenceController; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; +import com.android.settingslib.applications.StorageStatsSource; 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 { +public class StorageDashboardFragment extends DashboardFragment + implements LoaderManager.LoaderCallbacks> { private static final String TAG = "StorageDashboardFrag"; - private static final int APPS_JOB_ID = 0; + private static final int STORAGE_JOB_ID = 0; private VolumeInfo mVolume; private StorageSummaryDonutPreferenceController mSummaryController; private StorageItemPreferenceController mPreferenceController; + private List mSecondaryUsers; private boolean isVolumeValid() { return (mVolume != null) && (mVolume.getType() == VolumeInfo.TYPE_PRIVATE) @@ -59,7 +69,29 @@ public class StorageDashboardFragment extends DashboardFragment { @Override public void onResume() { super.onResume(); - getLoaderManager().initLoader(APPS_JOB_ID, Bundle.EMPTY, mPreferenceController); + getLoaderManager().initLoader(STORAGE_JOB_ID, Bundle.EMPTY, this); + } + + @Override + public Loader> 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> loader, + SparseArray data) { + mPreferenceController.onLoadFinished(data.get(UserHandle.myUserId())); + updateSecondaryUserControllers(mSecondaryUsers, data); + } + + @Override + public void onLoaderReset(Loader> loader) { } @Override @@ -117,7 +149,9 @@ public class StorageDashboardFragment extends DashboardFragment { UserManagerWrapper userManager = new UserManagerWrapperImpl(context.getSystemService(UserManager.class)); - SecondaryUserController.addAllSecondaryUserControllers(context, userManager, controllers); + mSecondaryUsers = SecondaryUserController.getSecondaryUserControllers(context, userManager); + controllers.addAll(mSecondaryUsers); + controllers.add(new ManageStoragePreferenceController(context)); return controllers; } @@ -133,6 +167,24 @@ public class StorageDashboardFragment extends DashboardFragment { return isVolumeValid(); } + /** + * Updates the secondary user controller sizes. + */ + private void updateSecondaryUserControllers(List controllers, + SparseArray 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); + } + } + } + } + /** * For Search. */ diff --git a/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java b/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java index b27dfa36058..d45c6e394cd 100644 --- a/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java +++ b/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java @@ -18,7 +18,8 @@ package com.android.settings.deviceinfo.storage; import android.content.Context; import android.content.pm.UserInfo; -import android.os.UserManager; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; import android.support.v7.preference.PreferenceGroup; import android.support.v7.preference.PreferenceScreen; @@ -27,6 +28,7 @@ import com.android.settings.Utils; import com.android.settings.applications.UserManagerWrapper; import com.android.settings.core.PreferenceController; +import java.util.ArrayList; import java.util.List; /** @@ -37,24 +39,27 @@ public class SecondaryUserController extends PreferenceController { // 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 SIZE_NOT_SET = -1; - private UserInfo mUser; - private StorageItemPreferenceAlternate mPreference; + private @NonNull UserInfo mUser; + private @Nullable StorageItemPreferenceAlternate mStoragePreference; + private long mSize; /** * Adds the appropriate controllers to a controller list for handling all secondary users on * a device. * @param context Context for initializing the preference controllers. - * @param controllers List of preference controllers for a Settings fragment. + * @param userManager UserManagerWrapper for figuring out which controllers to add. */ - public static void addAllSecondaryUserControllers(Context context, - UserManagerWrapper userManager, List controllers) { + public static List getSecondaryUserControllers( + Context context, UserManagerWrapper userManager) { + List controllers = new ArrayList<>(); UserInfo primaryUser = userManager.getPrimaryUser(); boolean addedUser = false; List infos = userManager.getUsers(); for (int i = 0, size = infos.size(); i < size; i++) { UserInfo info = infos.get(i); - if (Utils.isProfileOf(primaryUser, info)) { + if (info == null || Utils.isProfileOf(primaryUser, info)) { continue; } @@ -65,6 +70,7 @@ public class SecondaryUserController extends PreferenceController { if (!addedUser) { controllers.add(new NoSecondaryUserController(context)); } + return controllers; } /** @@ -73,22 +79,26 @@ public class SecondaryUserController extends PreferenceController { * @param info {@link UserInfo} for the secondary user which this controllers covers. */ @VisibleForTesting - SecondaryUserController(Context context, UserInfo info) { + SecondaryUserController(Context context, @NonNull UserInfo info) { super(context); mUser = info; + mSize = SIZE_NOT_SET; } @Override public void displayPreference(PreferenceScreen screen) { - if (mPreference == null) { - mPreference = new StorageItemPreferenceAlternate(mContext); + if (mStoragePreference == null) { + mStoragePreference = new StorageItemPreferenceAlternate(mContext); PreferenceGroup group = (PreferenceGroup) screen.findPreference(TARGET_PREFERENCE_GROUP_KEY); - mPreference.setTitle(mUser.name); - mPreference.setKey(PREFERENCE_KEY_BASE + mUser.id); + mStoragePreference.setTitle(mUser.name); + mStoragePreference.setKey(PREFERENCE_KEY_BASE + mUser.id); + if (mSize != SIZE_NOT_SET) { + mStoragePreference.setStorageSize(mSize); + } group.setVisible(true); - group.addPreference(mPreference); + group.addPreference(mStoragePreference); } } @@ -99,7 +109,15 @@ public class SecondaryUserController extends PreferenceController { @Override public String getPreferenceKey() { - return (mPreference != null) ? mPreference.getKey() : null; + return mStoragePreference != null ? mStoragePreference.getKey() : null; + } + + /** + * Returns the user for which this is the secondary user controller. + */ + @NonNull + public UserInfo getUser() { + return mUser; } /** @@ -107,8 +125,9 @@ public class SecondaryUserController extends PreferenceController { * @param size Size in bytes. */ public void setSize(long size) { - if (mPreference != null) { - mPreference.setStorageSize(size); + mSize = size; + if (mStoragePreference != null) { + mStoragePreference.setStorageSize(mSize); } } diff --git a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java index b16590e0583..4e39bb303e6 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java +++ b/src/com/android/settings/deviceinfo/storage/StorageAsyncLoader.java @@ -21,46 +21,61 @@ import static android.content.pm.ApplicationInfo.CATEGORY_GAME; 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 com.android.settings.applications.PackageManagerWrapper; +import com.android.settings.applications.UserManagerWrapper; import com.android.settings.utils.AsyncLoader; +import com.android.settingslib.applications.StorageStatsSource; import java.util.List; +import java.util.Map; /** - * AppsAsyncLoader is a Loader which loads app storage information and categories it by the app's - * specified categorization. + * StorageAsyncLoader is a Loader which loads categorized app information and external stats for all + * users */ -public class StorageAsyncLoader extends AsyncLoader { - private int mUserId; +public class StorageAsyncLoader + extends AsyncLoader> { + private UserManagerWrapper mUserManager; private String mUuid; private StorageStatsSource mStatsManager; private PackageManagerWrapper mPackageManager; - public StorageAsyncLoader(Context context, int userId, String uuid, StorageStatsSource source, - PackageManagerWrapper pm) { + public StorageAsyncLoader(Context context, UserManagerWrapper userManager, + String uuid, StorageStatsSource source, PackageManagerWrapper pm) { super(context); - mUserId = userId; + mUserManager = userManager; mUuid = uuid; mStatsManager = source; mPackageManager = pm; } @Override - public AppsStorageResult loadInBackground() { + public SparseArray loadInBackground() { return loadApps(); } - private AppsStorageResult loadApps() { - AppsStorageResult result = new AppsStorageResult(); - ArraySet seenUid = new ArraySet<>(); // some apps share a uid + private SparseArray loadApps() { + SparseArray result = new SparseArray<>(); + List infos = mUserManager.getUsers(); + for (int i = 0, userCount = infos.size(); i < userCount; i++) { + UserInfo info = infos.get(i); + result.put(info.id, getStorageResultForUser(info.id)); + } + return result; + } + private AppsStorageResult getStorageResultForUser(int userId) { List applicationInfos = - mPackageManager.getInstalledApplicationsAsUser(0, mUserId); - int size = applicationInfos.size(); - for (int i = 0; i < size; i++) { + mPackageManager.getInstalledApplicationsAsUser(0, userId); + ArraySet seenUid = new ArraySet<>(); // some apps share a uid + AppsStorageResult result = new AppsStorageResult(); + for (int i = 0, size = applicationInfos.size(); i < size; i++) { ApplicationInfo app = applicationInfos.get(i); if (seenUid.contains(app.uid)) { continue; @@ -83,12 +98,12 @@ public class StorageAsyncLoader extends AsyncLoader result) { } public static class AppsStorageResult { diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceAlternate.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceAlternate.java index 932a779d5e9..d5a36b92cad 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceAlternate.java +++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceAlternate.java @@ -18,10 +18,8 @@ package com.android.settings.deviceinfo.storage; import android.content.Context; import android.support.v7.preference.Preference; -import android.support.v7.preference.PreferenceViewHolder; import android.text.format.Formatter; import android.util.AttributeSet; -import android.view.View; import com.android.settings.R; diff --git a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java index 7afd0618870..88ba152e255 100644 --- a/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java +++ b/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceController.java @@ -17,11 +17,9 @@ package com.android.settings.deviceinfo.storage; import android.app.Fragment; -import android.app.LoaderManager; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; -import android.content.Loader; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -36,22 +34,21 @@ import com.android.settings.R; import com.android.settings.Settings; import com.android.settings.Utils; import com.android.settings.applications.ManageApplications; -import com.android.settings.applications.PackageManagerWrapperImpl; 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.HashMap; +import java.util.Map; /** * StorageItemPreferenceController handles the storage line items which summarize the storage * categorization breakdown. */ -public class StorageItemPreferenceController extends PreferenceController - implements LoaderManager.LoaderCallbacks { +public class StorageItemPreferenceController extends PreferenceController { private static final String TAG = "StorageItemPreference"; private static final String IMAGE_MIME_TYPE = "image/*"; @@ -170,17 +167,7 @@ public class StorageItemPreferenceController extends PreferenceController mFilePreference = (StorageItemPreferenceAlternate) screen.findPreference(FILES_KEY); } - @Override - public Loader onCreateLoader(int id, - Bundle args) { - return new StorageAsyncLoader(mContext, UserHandle.myUserId(), mVolume.fsUuid, - new StorageStatsSource(mContext), - new PackageManagerWrapperImpl(mContext.getPackageManager())); - } - - @Override - public void onLoadFinished(Loader loader, - StorageAsyncLoader.AppsStorageResult data) { + public void onLoadFinished(StorageAsyncLoader.AppsStorageResult data) { mPhotoPreference.setStorageSize( data.externalStats.imageBytes + data.externalStats.videoBytes); mAudioPreference.setStorageSize(data.musicAppsSize + data.externalStats.audioBytes); @@ -193,10 +180,6 @@ public class StorageItemPreferenceController extends PreferenceController mFilePreference.setStorageSize(unattributedBytes); } - @Override - public void onLoaderReset(Loader loader) { - } - /** * Sets the system size for the system size preference. * @param systemSize the size of the system in bytes @@ -266,7 +249,7 @@ public class StorageItemPreferenceController extends PreferenceController private static long totalValues(StorageMeasurement.MeasurementDetails details, int userId, String... keys) { long total = 0; - HashMap map = details.mediaSize.get(userId); + Map map = details.mediaSize.get(userId); if (map != null) { for (String key : keys) { if (map.containsKey(key)) { diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java index 43942a63d5e..b11132de19e 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java @@ -44,6 +44,7 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import java.util.ArrayList; +import java.util.List; @RunWith(SettingsRobolectricTestRunner.class) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) @@ -103,9 +104,8 @@ public class SecondaryUserControllerTest { userInfos.add(mPrimaryUser); when(mUserManager.getPrimaryUser()).thenReturn(mPrimaryUser); when(mUserManager.getUsers()).thenReturn(userInfos); - ArrayList controllers = new ArrayList<>(); - - SecondaryUserController.addAllSecondaryUserControllers(mContext, mUserManager, controllers); + List controllers = + SecondaryUserController.getSecondaryUserControllers(mContext, mUserManager); assertThat(controllers).hasSize(1); // We should have the NoSecondaryUserController. @@ -122,9 +122,8 @@ public class SecondaryUserControllerTest { userInfos.add(secondaryUser); when(mUserManager.getPrimaryUser()).thenReturn(mPrimaryUser); when(mUserManager.getUsers()).thenReturn(userInfos); - ArrayList controllers = new ArrayList<>(); - - SecondaryUserController.addAllSecondaryUserControllers(mContext, mUserManager, controllers); + List controllers = + SecondaryUserController.getSecondaryUserControllers(mContext, mUserManager); assertThat(controllers).hasSize(1); assertThat(controllers.get(0) instanceof SecondaryUserController).isTrue(); @@ -139,9 +138,9 @@ public class SecondaryUserControllerTest { userInfos.add(secondaryUser); when(mUserManager.getPrimaryUser()).thenReturn(mPrimaryUser); when(mUserManager.getUsers()).thenReturn(userInfos); - ArrayList controllers = new ArrayList<>(); - SecondaryUserController.addAllSecondaryUserControllers(mContext, mUserManager, controllers); + List controllers = + SecondaryUserController.getSecondaryUserControllers(mContext, mUserManager); assertThat(controllers).hasSize(1); assertThat(controllers.get(0) instanceof SecondaryUserController).isFalse(); diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java index d9e1f6df8c4..4622850d6b5 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/StorageItemPreferenceControllerTest.java @@ -44,6 +44,7 @@ import com.android.settings.TestConfig; import com.android.settings.applications.ManageApplications; import com.android.settings.core.instrumentation.MetricsFeatureProvider; import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settingslib.applications.StorageStatsSource; import com.android.settingslib.deviceinfo.StorageVolumeProvider; import org.junit.Before; @@ -223,7 +224,7 @@ public class StorageItemPreferenceControllerTest { result.gamesSize = KILOBYTE * 8; result.otherAppsSize = KILOBYTE * 9; - mController.onLoadFinished(null, result); + mController.onLoadFinished(result); assertThat(audio.getSummary().toString()).isEqualTo("14.00KB"); // 4KB apps + 10KB files assertThat(image.getSummary().toString()).isEqualTo("35.00KB"); // 15KB video + 20KB images diff --git a/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java b/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java index 71a15906854..70e05d65962 100644 --- a/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java +++ b/tests/unit/src/com/android/settings/deviceinfo/storage/StorageAsyncLoaderTest.java @@ -26,10 +26,15 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.UserInfo; +import android.os.UserHandle; import android.support.test.filters.SmallTest; import android.support.test.runner.AndroidJUnit4; +import android.util.SparseArray; import com.android.settings.applications.PackageManagerWrapper; +import com.android.settings.applications.UserManagerWrapper; +import com.android.settingslib.applications.StorageStatsSource; import org.junit.Before; import org.junit.Test; @@ -39,26 +44,39 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.ArrayList; +import java.util.List; +import java.util.Map; @RunWith(AndroidJUnit4.class) @SmallTest public class StorageAsyncLoaderTest { + private static final int PRIMARY_USER_ID = 0; + private static final int SECONDARY_USER_ID = 10; + @Mock private StorageStatsSource mSource; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mContext; @Mock private PackageManagerWrapper mPackageManager; - ArrayList mInfo = new ArrayList<>(); + @Mock + private UserManagerWrapper mUserManager; + private List mInfo = new ArrayList<>(); + private List mUsers; private StorageAsyncLoader mLoader; + @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); mInfo = new ArrayList<>(); - mLoader = new StorageAsyncLoader(mContext, 1, "id", mSource, mPackageManager); + mLoader = new StorageAsyncLoader(mContext, mUserManager, "id", mSource, mPackageManager); when(mPackageManager.getInstalledApplicationsAsUser(anyInt(), anyInt())).thenReturn(mInfo); + UserInfo info = new UserInfo(); + mUsers = new ArrayList<>(); + mUsers.add(info); + when(mUserManager.getUsers()).thenReturn(mUsers); } @Test @@ -66,20 +84,22 @@ public class StorageAsyncLoaderTest { addPackage(1001, 0, 1, 10, ApplicationInfo.CATEGORY_UNDEFINED); addPackage(1002, 0, 100, 1000, ApplicationInfo.CATEGORY_UNDEFINED); - StorageAsyncLoader.AppsStorageResult result = mLoader.loadInBackground(); + SparseArray result = mLoader.loadInBackground(); - assertThat(result.gamesSize).isEqualTo(0L); - assertThat(result.otherAppsSize).isEqualTo(1111L); + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(PRIMARY_USER_ID).gamesSize).isEqualTo(0L); + assertThat(result.get(PRIMARY_USER_ID).otherAppsSize).isEqualTo(1111L); } @Test public void testGamesAreFiltered() throws Exception { addPackage(1001, 0, 1, 10, ApplicationInfo.CATEGORY_GAME); - StorageAsyncLoader.AppsStorageResult result = mLoader.loadInBackground(); + SparseArray result = mLoader.loadInBackground(); - assertThat(result.gamesSize).isEqualTo(11L); - assertThat(result.otherAppsSize).isEqualTo(0); + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(PRIMARY_USER_ID).gamesSize).isEqualTo(11L); + assertThat(result.get(PRIMARY_USER_ID).otherAppsSize).isEqualTo(0); } @Test @@ -87,18 +107,37 @@ public class StorageAsyncLoaderTest { addPackage(1001, 0, 1, 10, ApplicationInfo.CATEGORY_UNDEFINED); addPackage(1001, 0, 1, 10, ApplicationInfo.CATEGORY_UNDEFINED); - StorageAsyncLoader.AppsStorageResult result = mLoader.loadInBackground(); + SparseArray result = mLoader.loadInBackground(); - assertThat(result.otherAppsSize).isEqualTo(11L); + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(PRIMARY_USER_ID).otherAppsSize).isEqualTo(11L); } @Test public void testCacheIsIgnored() throws Exception { addPackage(1001, 100, 1, 10, ApplicationInfo.CATEGORY_UNDEFINED); - StorageAsyncLoader.AppsStorageResult result = mLoader.loadInBackground(); + SparseArray result = mLoader.loadInBackground(); - assertThat(result.otherAppsSize).isEqualTo(11L); + assertThat(result.size()).isEqualTo(1); + assertThat(result.get(PRIMARY_USER_ID).otherAppsSize).isEqualTo(11L); + } + + @Test + public void testMultipleUsers() throws Exception { + UserInfo info = new UserInfo(); + info.id = SECONDARY_USER_ID; + mUsers.add(info); + when(mSource.getExternalStorageStats(anyString(), eq(UserHandle.SYSTEM))) + .thenReturn(new StorageStatsSource.ExternalStorageStats(9, 2, 3, 4)); + when(mSource.getExternalStorageStats(anyString(), eq(new UserHandle(SECONDARY_USER_ID)))) + .thenReturn(new StorageStatsSource.ExternalStorageStats(10, 3, 3, 4)); + + SparseArray result = mLoader.loadInBackground(); + + assertThat(result.size()).isEqualTo(2); + assertThat(result.get(PRIMARY_USER_ID).externalStats.totalBytes).isEqualTo(9L); + assertThat(result.get(SECONDARY_USER_ID).externalStats.totalBytes).isEqualTo(10L); } private void addPackage(int uid, long cacheSize, long codeSize, long dataSize, int category) {