1 /* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settingslib.users; 18 19 import android.app.Activity; 20 import android.app.AlertDialog; 21 import android.app.Dialog; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.graphics.Bitmap; 25 import android.graphics.drawable.Drawable; 26 import android.os.Bundle; 27 import android.os.UserHandle; 28 import android.view.LayoutInflater; 29 import android.view.View; 30 import android.view.WindowManager; 31 import android.widget.EditText; 32 import android.widget.ImageView; 33 34 import androidx.annotation.Nullable; 35 import androidx.annotation.VisibleForTesting; 36 37 import com.android.internal.util.UserIcons; 38 import com.android.settingslib.R; 39 import com.android.settingslib.drawable.CircleFramedDrawable; 40 41 import java.io.File; 42 import java.util.function.BiConsumer; 43 44 /** 45 * This class encapsulates a Dialog for editing the user nickname and photo. 46 */ 47 public class EditUserInfoController { 48 49 private static final String KEY_AWAITING_RESULT = "awaiting_result"; 50 private static final String KEY_SAVED_PHOTO = "pending_photo"; 51 52 private Dialog mEditUserInfoDialog; 53 private Bitmap mSavedPhoto; 54 private EditUserPhotoController mEditUserPhotoController; 55 private boolean mWaitingForActivityResult = false; 56 private final String mFileAuthority; 57 EditUserInfoController(String fileAuthority)58 public EditUserInfoController(String fileAuthority) { 59 mFileAuthority = fileAuthority; 60 } 61 clear()62 private void clear() { 63 if (mEditUserPhotoController != null) { 64 mEditUserPhotoController.removeNewUserPhotoBitmapFile(); 65 } 66 mEditUserInfoDialog = null; 67 mSavedPhoto = null; 68 } 69 70 /** 71 * This should be called when the container activity/fragment got re-initialized from a 72 * previously saved state. 73 */ onRestoreInstanceState(Bundle icicle)74 public void onRestoreInstanceState(Bundle icicle) { 75 String pendingPhoto = icicle.getString(KEY_SAVED_PHOTO); 76 if (pendingPhoto != null) { 77 mSavedPhoto = EditUserPhotoController.loadNewUserPhotoBitmap(new File(pendingPhoto)); 78 } 79 mWaitingForActivityResult = icicle.getBoolean(KEY_AWAITING_RESULT, false); 80 } 81 82 /** 83 * Should be called from the container activity/fragment when it's onSaveInstanceState is 84 * called. 85 */ onSaveInstanceState(Bundle outState)86 public void onSaveInstanceState(Bundle outState) { 87 if (mEditUserInfoDialog != null && mEditUserPhotoController != null) { 88 // Bitmap cannot be stored into bundle because it may exceed parcel limit 89 // Store it in a temporary file instead 90 File file = mEditUserPhotoController.saveNewUserPhotoBitmap(); 91 if (file != null) { 92 outState.putString(KEY_SAVED_PHOTO, file.getPath()); 93 } 94 } 95 outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult); 96 } 97 98 /** 99 * Should be called from the container activity/fragment when an activity has started for 100 * take/choose/crop photo actions. 101 */ startingActivityForResult()102 public void startingActivityForResult() { 103 mWaitingForActivityResult = true; 104 } 105 106 /** 107 * Should be called from the container activity/fragment after it receives a result from 108 * take/choose/crop photo activity. 109 */ onActivityResult(int requestCode, int resultCode, Intent data)110 public void onActivityResult(int requestCode, int resultCode, Intent data) { 111 mWaitingForActivityResult = false; 112 113 if (mEditUserPhotoController != null && mEditUserInfoDialog != null) { 114 mEditUserPhotoController.onActivityResult(requestCode, resultCode, data); 115 } 116 } 117 118 /** 119 * Creates a user edit dialog with option to change the user's name and photo. 120 * 121 * @param activityStarter - ActivityStarter is called with appropriate intents and request 122 * codes to take photo/choose photo/crop photo. 123 */ createDialog(Activity activity, ActivityStarter activityStarter, @Nullable Drawable oldUserIcon, String defaultUserName, String title, BiConsumer<String, Drawable> successCallback, Runnable cancelCallback)124 public Dialog createDialog(Activity activity, ActivityStarter activityStarter, 125 @Nullable Drawable oldUserIcon, String defaultUserName, String title, 126 BiConsumer<String, Drawable> successCallback, Runnable cancelCallback) { 127 LayoutInflater inflater = LayoutInflater.from(activity); 128 View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null); 129 130 EditText userNameView = content.findViewById(R.id.user_name); 131 userNameView.setText(defaultUserName); 132 133 ImageView userPhotoView = content.findViewById(R.id.user_photo); 134 135 // if oldUserIcon param is null then we use a default gray user icon 136 Drawable defaultUserIcon = oldUserIcon != null ? oldUserIcon : UserIcons.getDefaultUserIcon( 137 activity.getResources(), UserHandle.USER_NULL, false); 138 // in case a new photo was selected and the activity got recreated we have to load the image 139 Drawable userIcon = getUserIcon(activity, defaultUserIcon); 140 userPhotoView.setImageDrawable(userIcon); 141 142 if (canChangePhoto(activity)) { 143 mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter, 144 userPhotoView); 145 } else { 146 // some users can't change their photos so we need to remove suggestive 147 // background from the photoView 148 userPhotoView.setBackground(null); 149 } 150 151 mEditUserInfoDialog = buildDialog(activity, content, userNameView, oldUserIcon, 152 defaultUserName, title, successCallback, cancelCallback); 153 154 // Make sure the IME is up. 155 mEditUserInfoDialog.getWindow() 156 .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); 157 158 return mEditUserInfoDialog; 159 } 160 getUserIcon(Activity activity, Drawable defaultUserIcon)161 private Drawable getUserIcon(Activity activity, Drawable defaultUserIcon) { 162 if (mSavedPhoto != null) { 163 return CircleFramedDrawable.getInstance(activity, mSavedPhoto); 164 } 165 return defaultUserIcon; 166 } 167 buildDialog(Activity activity, View content, EditText userNameView, @Nullable Drawable oldUserIcon, String defaultUserName, String title, BiConsumer<String, Drawable> successCallback, Runnable cancelCallback)168 private Dialog buildDialog(Activity activity, View content, EditText userNameView, 169 @Nullable Drawable oldUserIcon, String defaultUserName, String title, 170 BiConsumer<String, Drawable> successCallback, Runnable cancelCallback) { 171 return new AlertDialog.Builder(activity) 172 .setTitle(title) 173 .setView(content) 174 .setCancelable(true) 175 .setPositiveButton(android.R.string.ok, (dialog, which) -> { 176 Drawable newUserIcon = mEditUserPhotoController != null 177 ? mEditUserPhotoController.getNewUserPhotoDrawable() 178 : null; 179 Drawable userIcon = newUserIcon != null 180 ? newUserIcon 181 : oldUserIcon; 182 183 String newName = userNameView.getText().toString().trim(); 184 String userName = !newName.isEmpty() ? newName : defaultUserName; 185 186 clear(); 187 if (successCallback != null) { 188 successCallback.accept(userName, userIcon); 189 } 190 }) 191 .setNegativeButton(android.R.string.cancel, (dialog, which) -> { 192 clear(); 193 if (cancelCallback != null) { 194 cancelCallback.run(); 195 } 196 }) 197 .setOnCancelListener(dialog -> { 198 clear(); 199 if (cancelCallback != null) { 200 cancelCallback.run(); 201 } 202 }) 203 .create(); 204 } 205 206 @VisibleForTesting canChangePhoto(Context context)207 boolean canChangePhoto(Context context) { 208 return (PhotoCapabilityUtils.canCropPhoto(context) 209 && PhotoCapabilityUtils.canChoosePhoto(context)) 210 || PhotoCapabilityUtils.canTakePhoto(context); 211 } 212 213 @VisibleForTesting createEditUserPhotoController(Activity activity, ActivityStarter activityStarter, ImageView userPhotoView)214 EditUserPhotoController createEditUserPhotoController(Activity activity, 215 ActivityStarter activityStarter, ImageView userPhotoView) { 216 return new EditUserPhotoController(activity, activityStarter, userPhotoView, 217 mSavedPhoto, mWaitingForActivityResult, mFileAuthority); 218 } 219 } 220