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
This commit is contained in:
Daniel Nishi
2017-03-24 12:23:27 -07:00
parent 2a3a92b513
commit fe33c58a18
8 changed files with 219 additions and 55 deletions

View File

@@ -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;

View File

@@ -17,6 +17,7 @@
package com.android.settings.applications;
import android.content.pm.UserInfo;
import java.util.List;
/**

View File

@@ -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<SparseArray<StorageAsyncLoader.AppsStorageResult>> {
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<PreferenceController> mSecondaryUsers;
@Override
public void onResume() {
super.onResume();
getLoaderManager().initLoader(STORAGE_JOB_ID, Bundle.EMPTY, this);
}
@Override
public Loader<SparseArray<StorageAsyncLoader.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<StorageAsyncLoader.AppsStorageResult>> loader,
SparseArray<StorageAsyncLoader.AppsStorageResult> data) {
mPreferenceController.onLoadFinished(data.get(UserHandle.myUserId()));
updateSecondaryUserControllers(mSecondaryUsers, data);
}
@Override
public void onLoaderReset(Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> 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<SparseArray<StorageAsyncLoader.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<StorageAsyncLoader.AppsStorageResult>> loader,
SparseArray<StorageAsyncLoader.AppsStorageResult> data) {
mPreferenceController.onLoadFinished(data.get(UserHandle.myUserId()));
updateSecondaryUserControllers(mSecondaryUsers, data);
}
@Override
public void onLoaderReset(Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> loader) {
}
/**
* IconLoaderCallbacks exists because StorageDashboardFragment already implements
* LoaderCallbacks for a different type.
*/
public final class IconLoaderCallbacks
implements LoaderManager.LoaderCallbacks<SparseArray<Drawable>> {
@Override
public Loader<SparseArray<Drawable>> onCreateLoader(int id, Bundle args) {
return new UserIconLoader(
getContext(),
() -> UserIconLoader.loadUserIconsWithContext(getContext()));
}
@Override
public void onLoadFinished(
Loader<SparseArray<Drawable>> loader, SparseArray<Drawable> data) {
mSecondaryUsers
.stream()
.filter(controller -> controller instanceof UserIconLoader.UserIconHandler)
.forEach(
controller ->
((UserIconLoader.UserIconHandler) controller)
.handleUserIcons(data));
}
@Override
public void onLoaderReset(Loader<SparseArray<Drawable>> loader) {}
}
}

View File

@@ -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<Drawable> 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);

View File

@@ -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<SparseArray<Drawable>> {
private FetchUserIconTask mTask;
/**
* Task to load all user icons.
*/
public interface FetchUserIconTask {
SparseArray<Drawable> getUserIcons();
}
/**
* Handle the output of this task.
*/
public interface UserIconHandler {
void handleUserIcons(SparseArray<Drawable> fetchedIcons);
}
public UserIconLoader(Context context, FetchUserIconTask task) {
super(context);
mTask = Preconditions.checkNotNull(task);
}
@Override
public SparseArray<Drawable> loadInBackground() {
return mTask.getUserIcons();
}
@Override
protected void onDiscardResult(SparseArray<Drawable> 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<Drawable> loadUserIconsWithContext(Context context) {
SparseArray<Drawable> 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;
}
}

View File

@@ -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<Drawable> fetchedIcons) {
Drawable userIcon = fetchedIcons.get(mUser.id);
if (userIcon != null) {
mStoragePreference.setIcon(userIcon);
}
}
}

View File

@@ -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<Drawable> 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<Preference> argumentCaptor = ArgumentCaptor.forClass(Preference.class);
verify(mGroup).addPreference(argumentCaptor.capture());
Preference preference = argumentCaptor.getValue();
assertThat(preference.getIcon()).isEqualTo(drawable);
}
}

View File

@@ -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<Preference> 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<Preference> 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<StorageAsyncLoader.AppsStorageResult> 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<Drawable> 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<Preference> argumentCaptor = ArgumentCaptor.forClass(Preference.class);
verify(mScreen).addPreference(argumentCaptor.capture());
Preference preference = argumentCaptor.getValue();
assertThat(preference.getIcon()).isEqualTo(drawable);
}
}