• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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