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