• 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.Dialog;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.graphics.Bitmap;
24 import android.graphics.drawable.Drawable;
25 import android.os.Bundle;
26 import android.os.UserHandle;
27 import android.os.UserManager;
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.NonNull;
35 import androidx.annotation.Nullable;
36 import androidx.annotation.VisibleForTesting;
37 
38 import com.android.internal.util.UserIcons;
39 import com.android.settingslib.R;
40 import com.android.settingslib.RestrictedLockUtils;
41 import com.android.settingslib.RestrictedLockUtilsInternal;
42 import com.android.settingslib.drawable.CircleFramedDrawable;
43 import com.android.settingslib.utils.CustomDialogHelper;
44 
45 import java.io.File;
46 import java.util.function.BiConsumer;
47 
48 /**
49  * This class encapsulates a Dialog for editing the user nickname and photo.
50  */
51 public class EditUserInfoController {
52 
53     private static final String KEY_AWAITING_RESULT = "awaiting_result";
54     private static final String KEY_SAVED_PHOTO = "pending_photo";
55 
56     private Dialog mEditUserInfoDialog;
57     private Bitmap mSavedPhoto;
58     private Drawable mSavedDrawable;
59     private EditUserPhotoController mEditUserPhotoController;
60     private boolean mWaitingForActivityResult = false;
61     private final String mFileAuthority;
62 
EditUserInfoController(String fileAuthority)63     public EditUserInfoController(String fileAuthority) {
64         mFileAuthority = fileAuthority;
65     }
66 
clear()67     private void clear() {
68         if (mEditUserPhotoController != null) {
69             mEditUserPhotoController.removeNewUserPhotoBitmapFile();
70         }
71         mEditUserInfoDialog = null;
72         mSavedPhoto = null;
73         mSavedDrawable = null;
74     }
75 
76     /**
77      * This should be called when the container activity/fragment got re-initialized from a
78      * previously saved state.
79      */
onRestoreInstanceState(Bundle icicle)80     public void onRestoreInstanceState(Bundle icicle) {
81         String pendingPhoto = icicle.getString(KEY_SAVED_PHOTO);
82         if (pendingPhoto != null) {
83             mSavedPhoto = EditUserPhotoController.loadNewUserPhotoBitmap(new File(pendingPhoto));
84         }
85         mWaitingForActivityResult = icicle.getBoolean(KEY_AWAITING_RESULT, false);
86     }
87 
88     /**
89      * Should be called from the container activity/fragment when it's onSaveInstanceState is
90      * called.
91      */
onSaveInstanceState(Bundle outState)92     public void onSaveInstanceState(Bundle outState) {
93         if (mEditUserInfoDialog != null && mEditUserPhotoController != null) {
94             // Bitmap cannot be stored into bundle because it may exceed parcel limit
95             // Store it in a temporary file instead
96             File file = mEditUserPhotoController.saveNewUserPhotoBitmap();
97             if (file != null) {
98                 outState.putString(KEY_SAVED_PHOTO, file.getPath());
99             }
100         }
101         outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult);
102     }
103 
104     /**
105      * Should be called from the container activity/fragment when an activity has started for
106      * take/choose/crop photo actions.
107      */
startingActivityForResult()108     public void startingActivityForResult() {
109         mWaitingForActivityResult = true;
110     }
111 
112     /**
113      * Should be called from the container activity/fragment after it receives a result from
114      * take/choose/crop photo activity.
115      */
onActivityResult(int requestCode, int resultCode, Intent data)116     public void onActivityResult(int requestCode, int resultCode, Intent data) {
117         mWaitingForActivityResult = false;
118 
119         if (mEditUserPhotoController != null && mEditUserInfoDialog != null) {
120             mEditUserPhotoController.onActivityResult(requestCode, resultCode, data);
121         }
122     }
123 
124     /**
125      * Creates a user edit dialog with option to change the user's name and photo.
126      *
127      * @param activityStarter - ActivityStarter is called with appropriate intents and request
128      *                        codes to take photo/choose photo/crop photo.
129      */
createDialog(@onNull Activity activity, @NonNull ActivityStarter activityStarter, @Nullable Drawable oldUserIcon, @Nullable String defaultUserName, @Nullable BiConsumer<String, Drawable> successCallback, @Nullable Runnable cancelCallback)130     public @NonNull Dialog createDialog(@NonNull Activity activity,
131             @NonNull ActivityStarter activityStarter, @Nullable Drawable oldUserIcon,
132             @Nullable String defaultUserName,
133             @Nullable BiConsumer<String, Drawable> successCallback,
134             @Nullable Runnable cancelCallback) {
135         LayoutInflater inflater = LayoutInflater.from(activity);
136         View content = inflater.inflate(R.layout.edit_user_info_dialog_content, null);
137 
138         EditText userNameView = content.findViewById(R.id.user_name);
139         userNameView.setText(defaultUserName);
140 
141         ImageView userPhotoView = content.findViewById(R.id.user_photo);
142 
143         // if oldUserIcon param is null then we use a default gray user icon
144         Drawable defaultUserIcon = oldUserIcon != null ? oldUserIcon : UserIcons.getDefaultUserIcon(
145                 activity.getResources(), UserHandle.USER_NULL, false);
146         // in case a new photo was selected and the activity got recreated we have to load the image
147         Drawable userIcon = getUserIcon(activity, defaultUserIcon);
148         userPhotoView.setImageDrawable(userIcon);
149 
150         if (isChangePhotoRestrictedByBase(activity)) {
151             // some users can't change their photos so we need to remove the suggestive icon
152             content.findViewById(R.id.add_a_photo_icon).setVisibility(View.GONE);
153         } else {
154             RestrictedLockUtils.EnforcedAdmin adminRestriction =
155                     getChangePhotoAdminRestriction(activity);
156             if (adminRestriction != null) {
157                 userPhotoView.setOnClickListener(view ->
158                         RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
159                                 activity, adminRestriction));
160             } else {
161                 mEditUserPhotoController = createEditUserPhotoController(activity, activityStarter,
162                         userPhotoView);
163             }
164         }
165         mEditUserInfoDialog = buildDialog(activity, content, userNameView, oldUserIcon,
166                 defaultUserName, successCallback, cancelCallback);
167 
168         // Make sure the IME is up.
169         mEditUserInfoDialog.getWindow()
170                 .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
171 
172         return mEditUserInfoDialog;
173     }
174 
getUserIcon(Activity activity, Drawable defaultUserIcon)175     private Drawable getUserIcon(Activity activity, Drawable defaultUserIcon) {
176         if (mSavedPhoto != null) {
177             mSavedDrawable = CircleFramedDrawable.getInstance(activity, mSavedPhoto);
178             return mSavedDrawable;
179         }
180         return defaultUserIcon;
181     }
182 
buildDialog(Activity activity, View content, EditText userNameView, @Nullable Drawable oldUserIcon, String defaultUserName, BiConsumer<String, Drawable> successCallback, Runnable cancelCallback)183     private Dialog buildDialog(Activity activity, View content, EditText userNameView,
184             @Nullable Drawable oldUserIcon, String defaultUserName,
185             BiConsumer<String, Drawable> successCallback, Runnable cancelCallback) {
186         CustomDialogHelper dialogHelper = new CustomDialogHelper(activity);
187         dialogHelper
188                 .setTitle(R.string.user_info_settings_title)
189                 .addCustomView(content)
190                 .setPositiveButton(R.string.okay, view -> {
191                     Drawable newUserIcon = mEditUserPhotoController != null
192                             ? mEditUserPhotoController.getNewUserPhotoDrawable()
193                             : null;
194                     Drawable userIcon = newUserIcon != null
195                             ? newUserIcon
196                             : oldUserIcon;
197 
198                     String newName = userNameView.getText().toString().trim();
199                     String userName = !newName.isEmpty() ? newName : defaultUserName;
200 
201                     clear();
202                     if (successCallback != null) {
203                         successCallback.accept(userName, userIcon);
204                     }
205                     dialogHelper.getDialog().dismiss();
206                 })
207                 .setBackButton(R.string.cancel, view -> {
208                     clear();
209                     if (cancelCallback != null) {
210                         cancelCallback.run();
211                     }
212                     dialogHelper.getDialog().dismiss();
213                 });
214         dialogHelper.getDialog().setOnCancelListener(dialog -> {
215             clear();
216             if (cancelCallback != null) {
217                 cancelCallback.run();
218             }
219             dialogHelper.getDialog().dismiss();
220         });
221         return dialogHelper.getDialog();
222     }
223 
224     @VisibleForTesting
isChangePhotoRestrictedByBase(Context context)225     boolean isChangePhotoRestrictedByBase(Context context) {
226         return RestrictedLockUtilsInternal.hasBaseUserRestriction(
227                 context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
228     }
229 
230     @VisibleForTesting
getChangePhotoAdminRestriction(Context context)231     RestrictedLockUtils.EnforcedAdmin getChangePhotoAdminRestriction(Context context) {
232         return RestrictedLockUtilsInternal.checkIfRestrictionEnforced(
233                 context, UserManager.DISALLOW_SET_USER_ICON, UserHandle.myUserId());
234     }
235 
236     @VisibleForTesting
createEditUserPhotoController(Activity activity, ActivityStarter activityStarter, ImageView userPhotoView)237     EditUserPhotoController createEditUserPhotoController(Activity activity,
238             ActivityStarter activityStarter, ImageView userPhotoView) {
239         return new EditUserPhotoController(activity, activityStarter, userPhotoView,
240                 mSavedPhoto, mSavedDrawable, mFileAuthority, false);
241     }
242 }
243