From fe33c58a1814d9c62474861a1e026461b8f82236 Mon Sep 17 00:00:00 2001 From: Daniel Nishi Date: Fri, 24 Mar 2017 12:23:27 -0700 Subject: [PATCH] Add user icon to the other user preferences. This icon is loaded in the background to avoid doing IO on the main thread. Once we know there are more users and also have the icon loaded, we add the category to the view. Fixes: 36252572 Test: Robotest Change-Id: Ib50287bb7ed323f22fbe407b56be1bf2fe98f121 --- src/com/android/settings/Utils.java | 1 - .../applications/UserManagerWrapper.java | 1 + .../deviceinfo/StorageDashboardFragment.java | 89 +++++++++++++------ .../storage/SecondaryUserController.java | 20 +++-- .../deviceinfo/storage/UserIconLoader.java | 75 ++++++++++++++++ .../storage/UserProfileController.java | 24 +++-- .../storage/SecondaryUserControllerTest.java | 26 +++++- .../storage/UserProfileControllerTest.java | 38 +++++--- 8 files changed, 219 insertions(+), 55 deletions(-) create mode 100644 src/com/android/settings/deviceinfo/storage/UserIconLoader.java diff --git a/src/com/android/settings/Utils.java b/src/com/android/settings/Utils.java index 4c8fb2d3414..d04ae8d0de9 100644 --- a/src/com/android/settings/Utils.java +++ b/src/com/android/settings/Utils.java @@ -73,7 +73,6 @@ import android.provider.ContactsContract.Data; import android.provider.ContactsContract.Profile; import android.provider.ContactsContract.RawContacts; import android.provider.Settings; -import android.service.persistentdata.PersistentDataBlockManager; import android.support.annotation.StringRes; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceGroup; diff --git a/src/com/android/settings/applications/UserManagerWrapper.java b/src/com/android/settings/applications/UserManagerWrapper.java index daefb844d91..5b4ed2aa05f 100644 --- a/src/com/android/settings/applications/UserManagerWrapper.java +++ b/src/com/android/settings/applications/UserManagerWrapper.java @@ -17,6 +17,7 @@ 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 75c0e753b05..602e65f802f 100644 --- a/src/com/android/settings/deviceinfo/StorageDashboardFragment.java +++ b/src/com/android/settings/deviceinfo/StorageDashboardFragment.java @@ -20,6 +20,7 @@ import android.app.LoaderManager; import android.content.Context; import android.content.Intent; import android.content.Loader; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; @@ -47,6 +48,7 @@ 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.deviceinfo.storage.UserIconLoader; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.search.Indexable; import com.android.settingslib.applications.StorageStatsSource; @@ -61,6 +63,7 @@ public class StorageDashboardFragment extends DashboardFragment implements LoaderManager.LoaderCallbacks> { private static final String TAG = "StorageDashboardFrag"; private static final int STORAGE_JOB_ID = 0; + private static final int ICON_JOB_ID = 1; private static final int OPTIONS_MENU_MIGRATE_DATA = 100; private VolumeInfo mVolume; @@ -70,34 +73,6 @@ public class StorageDashboardFragment extends DashboardFragment private PrivateVolumeOptionMenuController mOptionMenuController; private List mSecondaryUsers; - @Override - public void onResume() { - super.onResume(); - 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 public void onCreate(Bundle icicle) { super.onCreate(icicle); @@ -139,6 +114,13 @@ public class StorageDashboardFragment extends DashboardFragment } } + @Override + public void onResume() { + super.onResume(); + getLoaderManager().initLoader(STORAGE_JOB_ID, Bundle.EMPTY, this); + getLoaderManager().initLoader(ICON_JOB_ID, Bundle.EMPTY, new IconLoaderCallbacks()); + } + @Override public int getMetricsCategory() { return MetricsProto.MetricsEvent.SETTINGS_STORAGE_CATEGORY; @@ -227,4 +209,55 @@ public class StorageDashboardFragment extends DashboardFragment } }; + + @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) { + } + + /** + * IconLoaderCallbacks exists because StorageDashboardFragment already implements + * LoaderCallbacks for a different type. + */ + public final class IconLoaderCallbacks + implements LoaderManager.LoaderCallbacks> { + @Override + public Loader> onCreateLoader(int id, Bundle args) { + return new UserIconLoader( + getContext(), + () -> UserIconLoader.loadUserIconsWithContext(getContext())); + } + + @Override + public void onLoadFinished( + Loader> loader, SparseArray data) { + mSecondaryUsers + .stream() + .filter(controller -> controller instanceof UserIconLoader.UserIconHandler) + .forEach( + controller -> + ((UserIconLoader.UserIconHandler) controller) + .handleUserIcons(data)); + } + + @Override + public void onLoaderReset(Loader> loader) {} + } } diff --git a/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java b/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java index b3e89c65b9e..62e946d2690 100644 --- a/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java +++ b/src/com/android/settings/deviceinfo/storage/SecondaryUserController.java @@ -18,6 +18,7 @@ package com.android.settings.deviceinfo.storage; import android.content.Context; import android.content.pm.UserInfo; +import android.graphics.drawable.Drawable; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; @@ -37,8 +38,8 @@ import java.util.List; * SecondaryUserController controls the preferences on the Storage screen which had to do with * secondary users. */ -public class SecondaryUserController extends PreferenceController implements - StorageAsyncLoader.ResultHandler { +public class SecondaryUserController extends PreferenceController + implements 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 PREFERENCE_KEY_BASE = "pref_user_"; @@ -69,8 +70,9 @@ public class SecondaryUserController extends PreferenceController implements } if (info == null || Utils.isProfileOf(primaryUser, info)) { - controllers.add(new UserProfileController(context, info, - USER_PROFILE_INSERTION_LOCATION)); + controllers.add( + new UserProfileController( + context, info, userManager, USER_PROFILE_INSERTION_LOCATION)); continue; } @@ -109,8 +111,6 @@ public class SecondaryUserController extends PreferenceController implements mStoragePreference.setStorageSize(mSize, mTotalSizeBytes); } - // TODO(b/36252572): Set the user icon appropriately here. - group.setVisible(true); group.addPreference(mStoragePreference); } @@ -161,6 +161,14 @@ public class SecondaryUserController extends PreferenceController implements } } + @Override + public void handleUserIcons(SparseArray fetchedIcons) { + Drawable userIcon = fetchedIcons.get(mUser.id); + if (userIcon != null) { + mStoragePreference.setIcon(userIcon); + } + } + private static class NoSecondaryUserController extends PreferenceController { public NoSecondaryUserController(Context context) { super(context); diff --git a/src/com/android/settings/deviceinfo/storage/UserIconLoader.java b/src/com/android/settings/deviceinfo/storage/UserIconLoader.java new file mode 100644 index 00000000000..4f00c3c5496 --- /dev/null +++ b/src/com/android/settings/deviceinfo/storage/UserIconLoader.java @@ -0,0 +1,75 @@ +/* + * 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.graphics.drawable.Drawable; +import android.os.UserManager; +import android.util.Log; +import android.util.SparseArray; + +import com.android.internal.util.Preconditions; +import com.android.settings.Utils; +import com.android.settings.utils.AsyncLoader; + +/** + * Fetches a user icon as a loader using a given icon loading lambda. + */ +public class UserIconLoader extends AsyncLoader> { + private FetchUserIconTask mTask; + + /** + * Task to load all user icons. + */ + public interface FetchUserIconTask { + SparseArray getUserIcons(); + } + + /** + * Handle the output of this task. + */ + public interface UserIconHandler { + void handleUserIcons(SparseArray fetchedIcons); + } + + public UserIconLoader(Context context, FetchUserIconTask task) { + super(context); + mTask = Preconditions.checkNotNull(task); + } + + @Override + public SparseArray loadInBackground() { + return mTask.getUserIcons(); + } + + @Override + protected void onDiscardResult(SparseArray result) {} + + /** + * Loads the user icons using a given context. This returns a {@link SparseArray} which maps + * user ids to their user icons. + */ + public static SparseArray loadUserIconsWithContext(Context context) { + SparseArray value = new SparseArray<>(); + UserManager um = context.getSystemService(UserManager.class); + for (UserInfo userInfo : um.getUsers()) { + value.put(userInfo.id, Utils.getUserIcon(context, um, userInfo)); + } + return value; + } +} diff --git a/src/com/android/settings/deviceinfo/storage/UserProfileController.java b/src/com/android/settings/deviceinfo/storage/UserProfileController.java index 0e197400e68..18fa7b7df0d 100644 --- a/src/com/android/settings/deviceinfo/storage/UserProfileController.java +++ b/src/com/android/settings/deviceinfo/storage/UserProfileController.java @@ -19,6 +19,7 @@ package com.android.settings.deviceinfo.storage; import android.content.Context; import android.content.Intent; import android.content.pm.UserInfo; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.storage.VolumeInfo; import android.support.v7.preference.Preference; @@ -28,25 +29,27 @@ 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.applications.UserManagerWrapper; import com.android.settings.core.PreferenceController; import com.android.settings.deviceinfo.StorageItemPreference; 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 { +/** Defines a {@link PreferenceController} which handles a single profile of the primary user. */ +public class UserProfileController extends PreferenceController + implements StorageAsyncLoader.ResultHandler, UserIconLoader.UserIconHandler { private static final String PREFERENCE_KEY_BASE = "pref_profile_"; private StorageItemPreference mStoragePreference; + private UserManagerWrapper mUserManager; private UserInfo mUser; private long mTotalSizeBytes; private final int mPreferenceOrder; - public UserProfileController(Context context, UserInfo info, int preferenceOrder) { + public UserProfileController( + Context context, UserInfo info, UserManagerWrapper userManager, int preferenceOrder) { super(context); mUser = Preconditions.checkNotNull(info); + mUserManager = userManager; mPreferenceOrder = preferenceOrder; } @@ -66,7 +69,6 @@ public class UserProfileController extends PreferenceController implements mStoragePreference.setOrder(mPreferenceOrder); mStoragePreference.setKey(PREFERENCE_KEY_BASE + mUser.id); mStoragePreference.setTitle(mUser.name); - // TODO(b/36252572): Set user icon here. screen.addPreference(mStoragePreference); } @@ -110,4 +112,12 @@ public class UserProfileController extends PreferenceController implements public void setTotalSize(long totalSize) { mTotalSizeBytes = totalSize; } + + @Override + public void handleUserIcons(SparseArray fetchedIcons) { + Drawable userIcon = fetchedIcons.get(mUser.id); + if (userIcon != null) { + mStoragePreference.setIcon(userIcon); + } + } } 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 16bf1aef827..c55ba36052c 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/SecondaryUserControllerTest.java @@ -19,12 +19,14 @@ 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.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceGroup; import android.support.v7.preference.PreferenceScreen; @@ -34,7 +36,9 @@ import com.android.settings.SettingsRobolectricTestRunner; import com.android.settings.TestConfig; import com.android.settings.applications.UserManagerWrapper; import com.android.settings.core.PreferenceController; +import com.android.settingslib.R; import com.android.settingslib.applications.StorageStatsSource; +import com.android.settingslib.drawable.UserIconDrawable; import org.junit.Before; import org.junit.Test; @@ -187,4 +191,24 @@ public class SecondaryUserControllerTest { // We should have the NoSecondaryUserController. assertThat(controllers.get(0) instanceof SecondaryUserController).isFalse(); } + + @Test + public void iconCallbackChangesPreferenceIcon() throws Exception { + SparseArray icons = new SparseArray<>(); + Bitmap userBitmap = + BitmapFactory.decodeResource( + RuntimeEnvironment.application.getResources(), R.drawable.home); + UserIconDrawable drawable = new UserIconDrawable(100 /* size */).setIcon(userBitmap).bake(); + icons.put(10, drawable); + mPrimaryUser.name = TEST_NAME; + mPrimaryUser.id = 10; + mController.displayPreference(mScreen); + + mController.handleUserIcons(icons); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Preference.class); + verify(mGroup).addPreference(argumentCaptor.capture()); + Preference preference = argumentCaptor.getValue(); + assertThat(preference.getIcon()).isEqualTo(drawable); + } } diff --git a/tests/robotests/src/com/android/settings/deviceinfo/storage/UserProfileControllerTest.java b/tests/robotests/src/com/android/settings/deviceinfo/storage/UserProfileControllerTest.java index ed49da48027..0c3fc47216c 100644 --- a/tests/robotests/src/com/android/settings/deviceinfo/storage/UserProfileControllerTest.java +++ b/tests/robotests/src/com/android/settings/deviceinfo/storage/UserProfileControllerTest.java @@ -18,7 +18,6 @@ package com.android.settings.deviceinfo.storage; import static com.google.common.truth.Truth.assertThat; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -26,6 +25,9 @@ import static org.mockito.Mockito.when; import android.content.Context; import android.content.Intent; import android.content.pm.UserInfo; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; import android.support.v7.preference.Preference; import android.support.v7.preference.PreferenceScreen; import android.util.SparseArray; @@ -36,7 +38,9 @@ import com.android.settings.SubSettings; import com.android.settings.TestConfig; import com.android.settings.applications.UserManagerWrapper; import com.android.settings.deviceinfo.StorageProfileFragment; +import com.android.settingslib.R; import com.android.settingslib.applications.StorageStatsSource; +import com.android.settingslib.drawable.UserIconDrawable; import org.junit.Before; import org.junit.Test; @@ -66,16 +70,15 @@ public class UserProfileControllerTest { MockitoAnnotations.initMocks(this); mContext = spy(RuntimeEnvironment.application); mPrimaryProfile = new UserInfo(); - mController = new UserProfileController(mContext, mPrimaryProfile, 0); + mController = new UserProfileController(mContext, mPrimaryProfile, mUserManager, 0); when(mScreen.getContext()).thenReturn(mContext); + mPrimaryProfile.name = TEST_NAME; + mPrimaryProfile.id = 10; + mController.displayPreference(mScreen); } @Test public void controllerAddsPrimaryProfilePreference() throws Exception { - mPrimaryProfile.name = TEST_NAME; - mPrimaryProfile.id = 10; - mController.displayPreference(mScreen); - final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Preference.class); verify(mScreen).addPreference(argumentCaptor.capture()); Preference preference = argumentCaptor.getValue(); @@ -86,9 +89,6 @@ public class UserProfileControllerTest { @Test public void tappingProfilePreferenceSendsToStorageProfileFragment() throws Exception { - mPrimaryProfile.name = TEST_NAME; - mPrimaryProfile.id = 10; - mController.displayPreference(mScreen); final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Preference.class); verify(mScreen).addPreference(argumentCaptor.capture()); @@ -105,9 +105,6 @@ public class UserProfileControllerTest { @Test public void acceptingResultUpdatesPreferenceSize() throws Exception { - mPrimaryProfile.name = TEST_NAME; - mPrimaryProfile.id = 10; - mController.displayPreference(mScreen); SparseArray result = new SparseArray<>(); StorageAsyncLoader.AppsStorageResult userResult = new StorageAsyncLoader.AppsStorageResult(); @@ -121,4 +118,21 @@ public class UserProfileControllerTest { assertThat(preference.getSummary()).isEqualTo("99.00B"); } + + @Test + public void iconCallbackChangesPreferenceIcon() throws Exception { + SparseArray icons = new SparseArray<>(); + Bitmap userBitmap = + BitmapFactory.decodeResource( + RuntimeEnvironment.application.getResources(), R.drawable.home); + UserIconDrawable drawable = new UserIconDrawable(100 /* size */).setIcon(userBitmap).bake(); + icons.put(10, drawable); + + mController.handleUserIcons(icons); + + final ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Preference.class); + verify(mScreen).addPreference(argumentCaptor.capture()); + Preference preference = argumentCaptor.getValue(); + assertThat(preference.getIcon()).isEqualTo(drawable); + } } \ No newline at end of file