diff --git a/core/java/com/android/internal/util/UserIcons.java b/core/java/com/android/internal/util/UserIcons.java
index bfe43237da58d..17b84ffc2f3fa 100644
--- a/core/java/com/android/internal/util/UserIcons.java
+++ b/core/java/com/android/internal/util/UserIcons.java
@@ -16,6 +16,7 @@
package com.android.internal.util;
+import android.annotation.ColorInt;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
@@ -72,9 +73,30 @@ public class UserIcons {
// Return colored icon instead
colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length];
}
+ return getDefaultUserIconInColor(resources, resources.getColor(colorResId, null));
+ }
+
+ /**
+ * Returns a default user icon in a particular color.
+ *
+ * @param resources resources object to fetch the user icon
+ * @param color the color used for the icon
+ */
+ public static Drawable getDefaultUserIconInColor(Resources resources, @ColorInt int color) {
Drawable icon = resources.getDrawable(R.drawable.ic_account_circle, null).mutate();
- icon.setColorFilter(resources.getColor(colorResId, null), Mode.SRC_IN);
+ icon.setColorFilter(color, Mode.SRC_IN);
icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
return icon;
}
+
+ /**
+ * Returns an array containing colors to be used for default user icons.
+ */
+ public static int[] getUserIconColors(Resources resources) {
+ int[] result = new int[USER_ICON_COLORS.length];
+ for (int i = 0; i < result.length; i++) {
+ result[i] = resources.getColor(USER_ICON_COLORS[i], null);
+ }
+ return result;
+ }
}
diff --git a/packages/SettingsLib/res/drawable/avatar_selector.xml b/packages/SettingsLib/res/drawable/avatar_selector.xml
index b4eea209780b1..ccde59763a4ab 100644
--- a/packages/SettingsLib/res/drawable/avatar_selector.xml
+++ b/packages/SettingsLib/res/drawable/avatar_selector.xml
@@ -17,7 +17,9 @@
-
-
+
\ No newline at end of file
diff --git a/packages/SettingsLib/res/drawable/ic_account_circle.xml b/packages/SettingsLib/res/drawable/ic_account_circle_outline.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable/ic_account_circle.xml
rename to packages/SettingsLib/res/drawable/ic_account_circle_outline.xml
diff --git a/packages/SettingsLib/res/layout/avatar_picker.xml b/packages/SettingsLib/res/layout/avatar_picker.xml
index ffb32f70b14ed..2d40bd0de7eae 100644
--- a/packages/SettingsLib/res/layout/avatar_picker.xml
+++ b/packages/SettingsLib/res/layout/avatar_picker.xml
@@ -20,7 +20,7 @@
android:id="@+id/glif_layout"
android:layout_height="match_parent"
android:layout_width="match_parent"
- android:icon="@drawable/ic_account_circle"
+ android:icon="@drawable/ic_account_circle_outline"
app:sucUsePartnerResource="true"
app:sucHeaderText="@string/avatar_picker_title">
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
index 8dfd1bd5d05c6..61b8911acea45 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPhotoController.java
@@ -102,7 +102,7 @@ class AvatarPhotoController {
switch (requestCode) {
case REQUEST_CODE_CROP_PHOTO:
- mActivity.returnResult(pictureUri);
+ mActivity.returnUriResult(pictureUri);
return true;
case REQUEST_CODE_TAKE_PHOTO:
case REQUEST_CODE_CHOOSE_PHOTO:
@@ -232,7 +232,7 @@ class AvatarPhotoController {
@Override
protected void onPostExecute(Bitmap bitmap) {
saveBitmapToFile(bitmap, new File(mImagesDir, CROP_PICTURE_FILE_NAME));
- mActivity.returnResult(mCropPictureUri);
+ mActivity.returnUriResult(mCropPictureUri);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
index 2d9809d2f69ed..50015e6533999 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/AvatarPickerActivity.java
@@ -36,6 +36,7 @@ import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.internal.util.UserIcons;
import com.android.settingslib.R;
import com.google.android.setupcompat.template.FooterBarMixin;
@@ -43,12 +44,29 @@ import com.google.android.setupcompat.template.FooterButton;
import com.google.android.setupdesign.GlifLayout;
import com.google.android.setupdesign.util.ThemeHelper;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Activity to allow the user to choose a user profile picture.
+ *
+ *
Options are provided to take a photo or choose a photo using the photo picker. In addition,
+ * preselected avatar images may be provided in the resource array {@code avatar_images}. If
+ * provided, every element of that array must be a bitmap drawable.
+ *
+ *
If preselected images are not provided, the default avatar will be shown instead, in a range
+ * of colors.
+ *
+ *
This activity should be started with startActivityForResult. If a photo or a preselected image
+ * is selected, a Uri will be returned in the data field of the result intent. If a colored default
+ * avatar is selected, the chosen color will be returned as {@code EXTRA_DEFAULT_ICON_TINT_COLOR}
+ * and the data field will be empty.
*/
public class AvatarPickerActivity extends Activity {
static final String EXTRA_FILE_AUTHORITY = "file_authority";
+ static final String EXTRA_DEFAULT_ICON_TINT_COLOR = "default_icon_tint_color";
+
private static final String KEY_AWAITING_RESULT = "awaiting_result";
private static final String KEY_SELECTED_POSITION = "selected_position";
@@ -91,7 +109,7 @@ public class AvatarPickerActivity extends Activity {
mDoneButton =
new FooterButton.Builder(this)
.setText("Done")
- .setListener(view -> returnResult(mAdapter.uriForSelection()))
+ .setListener(view -> mAdapter.returnSelectionResult())
.build();
mDoneButton.setEnabled(false);
@@ -134,13 +152,20 @@ public class AvatarPickerActivity extends Activity {
super.startActivityForResult(intent, requestCode);
}
- void returnResult(Uri uri) {
+ void returnUriResult(Uri uri) {
Intent resultData = new Intent();
resultData.setData(uri);
setResult(RESULT_OK, resultData);
finish();
}
+ void returnColorResult(int color) {
+ Intent resultData = new Intent();
+ resultData.putExtra(EXTRA_DEFAULT_ICON_TINT_COLOR, color);
+ setResult(RESULT_OK, resultData);
+ finish();
+ }
+
private void cancel() {
setResult(RESULT_CANCELED);
finish();
@@ -154,7 +179,9 @@ public class AvatarPickerActivity extends Activity {
private final int mChoosePhotoPosition;
private final int mPreselectedImageStartPosition;
- private final TypedArray mImageDrawables;
+ private final List mImageDrawables;
+ private final TypedArray mPreselectedImages;
+ private final int[] mUserIconColors;
private int mSelectedPosition = NONE;
AvatarAdapter() {
@@ -166,7 +193,9 @@ public class AvatarPickerActivity extends Activity {
mChoosePhotoPosition = (canChoosePhoto ? (canTakePhoto ? 1 : 0) : NONE);
mPreselectedImageStartPosition = (canTakePhoto ? 1 : 0) + (canChoosePhoto ? 1 : 0);
- mImageDrawables = getResources().obtainTypedArray(R.array.avatar_images);
+ mPreselectedImages = getResources().obtainTypedArray(R.array.avatar_images);
+ mUserIconColors = UserIcons.getUserIconColors(getResources());
+ mImageDrawables = buildDrawableList();
}
@NonNull
@@ -188,14 +217,8 @@ public class AvatarPickerActivity extends Activity {
viewHolder.setClickListener(view -> mAvatarPhotoController.choosePhoto());
} else if (position >= mPreselectedImageStartPosition) {
- Drawable drawable = mImageDrawables.getDrawable(indexFromPosition(position));
- if (drawable instanceof BitmapDrawable) {
- drawable = circularDrawableFrom((BitmapDrawable) drawable);
- } else {
- throw new IllegalStateException("Avatar drawables must be bitmaps");
- }
viewHolder.setSelected(position == mSelectedPosition);
- viewHolder.setDrawable(drawable);
+ viewHolder.setDrawable(mImageDrawables.get(indexFromPosition(position)));
viewHolder.setClickListener(view -> {
if (mSelectedPosition == position) {
deselect(position);
@@ -208,7 +231,29 @@ public class AvatarPickerActivity extends Activity {
@Override
public int getItemCount() {
- return mPreselectedImageStartPosition + mImageDrawables.length();
+ return mPreselectedImageStartPosition + mImageDrawables.size();
+ }
+
+ private List buildDrawableList() {
+ List result = new ArrayList<>();
+
+ for (int i = 0; i < mPreselectedImages.length(); i++) {
+ Drawable drawable = mPreselectedImages.getDrawable(i);
+ if (drawable instanceof BitmapDrawable) {
+ result.add(circularDrawableFrom((BitmapDrawable) drawable));
+ } else {
+ throw new IllegalStateException("Avatar drawables must be bitmaps");
+ }
+ }
+ if (!result.isEmpty()) {
+ return result;
+ }
+
+ // No preselected images. Use tinted default icon.
+ for (int i = 0; i < mUserIconColors.length; i++) {
+ result.add(UserIcons.getDefaultUserIconInColor(getResources(), mUserIconColors[i]));
+ }
+ return result;
}
private Drawable circularDrawableFrom(BitmapDrawable drawable) {
@@ -242,13 +287,18 @@ public class AvatarPickerActivity extends Activity {
mDoneButton.setEnabled(false);
}
- private Uri uriForSelection() {
- int resourceId =
- mImageDrawables.getResourceId(indexFromPosition(mSelectedPosition), -1);
- if (resourceId == -1) {
- throw new IllegalStateException("Preselected avatar images must be resources.");
+ private void returnSelectionResult() {
+ int index = indexFromPosition(mSelectedPosition);
+ if (mPreselectedImages.length() > 0) {
+ int resourceId = mPreselectedImages.getResourceId(index, -1);
+ if (resourceId == -1) {
+ throw new IllegalStateException("Preselected avatar images must be resources.");
+ }
+ returnUriResult(uriForResourceId(resourceId));
+ } else {
+ returnColorResult(
+ mUserIconColors[index]);
}
- return uriForResourceId(resourceId);
}
private Uri uriForResourceId(int resourceId) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
index 03b6d2444f225..f8bb38b5978ec 100644
--- a/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/users/EditUserPhotoController.java
@@ -16,17 +16,21 @@
package com.android.settingslib.users;
+import android.annotation.NonNull;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.AsyncTask;
import android.util.Log;
import android.widget.ImageView;
+import com.android.internal.util.UserIcons;
+import com.android.settingslib.R;
import com.android.settingslib.drawable.CircleFramedDrawable;
+import com.android.settingslib.utils.ThreadUtils;
import java.io.File;
import java.io.FileNotFoundException;
@@ -34,6 +38,7 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.concurrent.ExecutionException;
/**
* This class contains logic for starting activities to take/choose/crop photo, reads and transforms
@@ -81,10 +86,17 @@ public class EditUserPhotoController {
}
if (requestCode == REQUEST_CODE_PICK_AVATAR) {
+ if (data.hasExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR)) {
+ int tintColor =
+ data.getIntExtra(AvatarPickerActivity.EXTRA_DEFAULT_ICON_TINT_COLOR, -1);
+ onDefaultIconSelected(tintColor);
+ return true;
+ }
if (data.getData() != null) {
onPhotoCropped(data.getData());
+ return true;
}
- return true;
+
}
return false;
}
@@ -99,36 +111,54 @@ public class EditUserPhotoController {
mActivityStarter.startActivityForResult(intent, REQUEST_CODE_PICK_AVATAR);
}
+ private void onDefaultIconSelected(int tintColor) {
+ try {
+ ThreadUtils.postOnBackgroundThread(() -> {
+ Drawable drawable =
+ UserIcons.getDefaultUserIconInColor(mActivity.getResources(), tintColor);
+ Bitmap bitmap = convertToBitmap(drawable,
+ (int) mActivity.getResources().getDimension(R.dimen.circle_avatar_size));
+
+ ThreadUtils.postOnMainThread(() -> onPhotoProcessed(bitmap));
+ }).get();
+ } catch (InterruptedException | ExecutionException e) {
+ Log.e(TAG, "Error processing default icon", e);
+ }
+ }
+
+ private static Bitmap convertToBitmap(@NonNull Drawable icon, int size) {
+ Bitmap bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ icon.setBounds(0, 0, size, size);
+ icon.draw(canvas);
+ return bitmap;
+ }
+
private void onPhotoCropped(final Uri data) {
- // TODO: Replace AsyncTask to avoid possible memory leaks and handle configuration change
- new AsyncTask() {
- @Override
- protected Bitmap doInBackground(Void... params) {
- InputStream imageStream = null;
- try {
- imageStream = mActivity.getContentResolver()
- .openInputStream(data);
- return BitmapFactory.decodeStream(imageStream);
- } catch (FileNotFoundException fe) {
- Log.w(TAG, "Cannot find image file", fe);
- return null;
- } finally {
- if (imageStream != null) {
- try {
- imageStream.close();
- } catch (IOException ioe) {
- Log.w(TAG, "Cannot close image stream", ioe);
- }
+ ThreadUtils.postOnBackgroundThread(() -> {
+ InputStream imageStream = null;
+ Bitmap bitmap = null;
+ try {
+ imageStream = mActivity.getContentResolver()
+ .openInputStream(data);
+ bitmap = BitmapFactory.decodeStream(imageStream);
+ } catch (FileNotFoundException fe) {
+ Log.w(TAG, "Cannot find image file", fe);
+ } finally {
+ if (imageStream != null) {
+ try {
+ imageStream.close();
+ } catch (IOException ioe) {
+ Log.w(TAG, "Cannot close image stream", ioe);
}
}
}
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- onPhotoProcessed(bitmap);
-
+ if (bitmap != null) {
+ Bitmap finalBitmap = bitmap;
+ ThreadUtils.postOnMainThread(() -> onPhotoProcessed(finalBitmap));
}
- }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
+ });
}
private void onPhotoProcessed(Bitmap bitmap) {