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.settings.users;
18 
19 import android.app.Activity;
20 import android.app.Dialog;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.Intent;
24 import android.content.pm.UserInfo;
25 import android.graphics.Bitmap;
26 import android.graphics.drawable.Drawable;
27 import android.os.Bundle;
28 import android.os.UserHandle;
29 import android.os.UserManager;
30 import android.text.TextUtils;
31 import android.view.LayoutInflater;
32 import android.view.View;
33 import android.view.WindowManager;
34 import android.widget.EditText;
35 import android.widget.ImageView;
36 
37 import androidx.annotation.VisibleForTesting;
38 import androidx.appcompat.app.AlertDialog;
39 import androidx.fragment.app.Fragment;
40 
41 import com.android.settings.R;
42 import com.android.settingslib.drawable.CircleFramedDrawable;
43 
44 import java.io.File;
45 
46 /**
47  * This class encapsulates a Dialog for editing the user nickname and photo.
48  */
49 public class EditUserInfoController {
50 
51     private static final String KEY_AWAITING_RESULT = "awaiting_result";
52     private static final String KEY_SAVED_PHOTO = "pending_photo";
53 
54     private Dialog mEditUserInfoDialog;
55     private Bitmap mSavedPhoto;
56     private EditUserPhotoController mEditUserPhotoController;
57     private UserHandle mUser;
58     private UserManager mUserManager;
59     private boolean mWaitingForActivityResult = false;
60 
61     /**
62      * Callback made when either the username text or photo choice changes.
63      */
64     public interface OnContentChangedCallback {
65         /** Photo updated. */
onPhotoChanged(UserHandle user, Drawable photo)66         void onPhotoChanged(UserHandle user, Drawable photo);
67         /** Username updated. */
onLabelChanged(UserHandle user, CharSequence label)68         void onLabelChanged(UserHandle user, CharSequence label);
69     }
70 
71     /**
72      * Callback made when the dialog finishes.
73      */
74     public interface OnDialogCompleteCallback {
75         /** Dialog closed with positive button. */
onPositive()76         void onPositive();
77         /** Dialog closed with negative button or cancelled. */
onNegativeOrCancel()78         void onNegativeOrCancel();
79     }
80 
clear()81     public void clear() {
82         if (mEditUserPhotoController != null) {
83             mEditUserPhotoController.removeNewUserPhotoBitmapFile();
84         }
85         mEditUserInfoDialog = null;
86         mSavedPhoto = null;
87     }
88 
getDialog()89     public Dialog getDialog() {
90         return mEditUserInfoDialog;
91     }
92 
onRestoreInstanceState(Bundle icicle)93     public void onRestoreInstanceState(Bundle icicle) {
94         String pendingPhoto = icicle.getString(KEY_SAVED_PHOTO);
95         if (pendingPhoto != null) {
96             mSavedPhoto = EditUserPhotoController.loadNewUserPhotoBitmap(new File(pendingPhoto));
97         }
98         mWaitingForActivityResult = icicle.getBoolean(KEY_AWAITING_RESULT, false);
99     }
100 
onSaveInstanceState(Bundle outState)101     public void onSaveInstanceState(Bundle outState) {
102         if (mEditUserInfoDialog != null && mEditUserInfoDialog.isShowing()
103                 && mEditUserPhotoController != null) {
104             // Bitmap cannot be stored into bundle because it may exceed parcel limit
105             // Store it in a temporary file instead
106             File file = mEditUserPhotoController.saveNewUserPhotoBitmap();
107             if (file != null) {
108                 outState.putString(KEY_SAVED_PHOTO, file.getPath());
109             }
110         }
111         if (mWaitingForActivityResult) {
112             outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult);
113         }
114     }
115 
startingActivityForResult()116     public void startingActivityForResult() {
117         mWaitingForActivityResult = true;
118     }
119 
onActivityResult(int requestCode, int resultCode, Intent data)120     public void onActivityResult(int requestCode, int resultCode, Intent data) {
121         mWaitingForActivityResult = false;
122 
123         if (mEditUserPhotoController != null && mEditUserInfoDialog != null) {
124             mEditUserPhotoController.onActivityResult(requestCode, resultCode, data);
125         }
126     }
127 
createDialog(final Fragment fragment, final Drawable currentUserIcon, final CharSequence currentUserName, String title, final OnContentChangedCallback callback, UserHandle user, OnDialogCompleteCallback completeCallback)128     public Dialog createDialog(final Fragment fragment, final Drawable currentUserIcon,
129             final CharSequence currentUserName,
130             String title, final OnContentChangedCallback callback, UserHandle user,
131             OnDialogCompleteCallback completeCallback) {
132         Activity activity = fragment.getActivity();
133         mUser = user;
134         if (mUserManager == null) {
135             mUserManager = activity.getSystemService(UserManager.class);
136         }
137         LayoutInflater inflater = activity.getLayoutInflater();
138         View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null);
139 
140         final EditText userNameView = (EditText) content.findViewById(R.id.user_name);
141         userNameView.setText(currentUserName);
142 
143         final ImageView userPhotoView = (ImageView) content.findViewById(R.id.user_photo);
144 
145         boolean canChangePhoto = mUserManager != null &&
146                 canChangePhoto(activity, mUserManager.getUserInfo(user.getIdentifier()));
147         if (!canChangePhoto) {
148             // some users can't change their photos so we need to remove suggestive
149             // background from the photoView
150             userPhotoView.setBackground(null);
151         }
152         Drawable drawable = null;
153         if (mSavedPhoto != null) {
154             drawable = CircleFramedDrawable.getInstance(activity, mSavedPhoto);
155         } else {
156             drawable = currentUserIcon;
157         }
158         userPhotoView.setImageDrawable(drawable);
159         if (canChangePhoto) {
160             mEditUserPhotoController =
161                     createEditUserPhotoController(fragment, userPhotoView, drawable);
162         }
163         mEditUserInfoDialog = new AlertDialog.Builder(activity)
164                 .setTitle(title)
165                 .setView(content)
166                 .setCancelable(true)
167                 .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
168                     @Override
169                     public void onClick(DialogInterface dialog, int which) {
170                         if (which == DialogInterface.BUTTON_POSITIVE) {
171                             // Update the name if changed.
172                             CharSequence userName = userNameView.getText();
173                             if (!TextUtils.isEmpty(userName)) {
174                                 if (currentUserName == null
175                                         || !userName.toString().equals(
176                                         currentUserName.toString())) {
177                                     if (callback != null) {
178                                         callback.onLabelChanged(mUser, userName.toString());
179                                     }
180                                 }
181                             }
182                             // Update the photo if changed.
183                             if (mEditUserPhotoController != null) {
184                                 Drawable drawable =
185                                         mEditUserPhotoController.getNewUserPhotoDrawable();
186                                 if (drawable != null && !drawable.equals(currentUserIcon)) {
187                                     if (callback != null) {
188                                         callback.onPhotoChanged(mUser, drawable);
189                                     }
190                                 }
191                             }
192                         }
193                         clear();
194                         if (completeCallback != null) {
195                             completeCallback.onPositive();
196                         }
197                     }
198                 })
199                 .setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
200                     @Override
201                     public void onClick(DialogInterface dialog, int which) {
202                         clear();
203                         if (completeCallback != null) {
204                             completeCallback.onNegativeOrCancel();
205                         }
206                     }
207                 })
208                 .setOnCancelListener(new DialogInterface.OnCancelListener() {
209                     @Override
210                     public void onCancel(DialogInterface dialog) {
211                         clear();
212                         if (completeCallback != null) {
213                             completeCallback.onNegativeOrCancel();
214                         }
215                     }
216                 })
217                 .create();
218 
219         // Make sure the IME is up.
220         mEditUserInfoDialog.getWindow().setSoftInputMode(
221                 WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
222 
223         return mEditUserInfoDialog;
224     }
225 
226     @VisibleForTesting
canChangePhoto(Context context, UserInfo user)227     boolean canChangePhoto(Context context, UserInfo user) {
228         return PhotoCapabilityUtils.canCropPhoto(context) &&
229                 (PhotoCapabilityUtils.canChoosePhoto(context)
230                         || PhotoCapabilityUtils.canTakePhoto(context));
231     }
232 
233     @VisibleForTesting
createEditUserPhotoController(Fragment fragment, ImageView userPhotoView, Drawable drawable)234     EditUserPhotoController createEditUserPhotoController(Fragment fragment,
235             ImageView userPhotoView, Drawable drawable) {
236         return new EditUserPhotoController(fragment, userPhotoView,
237                 mSavedPhoto, drawable, mWaitingForActivityResult);
238     }
239 }
240