1 /*
2  * Copyright (C) 2018 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 package com.android.emergency.preferences;
17 
18 import android.app.AlertDialog;
19 import android.app.Fragment;
20 import android.content.Context;
21 import android.content.DialogInterface;
22 import android.content.Intent;
23 import android.graphics.Bitmap;
24 import android.graphics.drawable.Drawable;
25 import android.os.AsyncTask;
26 import android.os.Bundle;
27 import android.os.UserHandle;
28 import android.os.UserManager;
29 import android.text.TextUtils;
30 import android.util.AttributeSet;
31 import android.view.View;
32 import android.widget.EditText;
33 import android.widget.ImageView;
34 import androidx.annotation.VisibleForTesting;
35 import androidx.preference.DialogPreference;
36 import com.android.emergency.CircleFramedDrawable;
37 import com.android.emergency.R;
38 import com.android.internal.util.UserIcons;
39 import com.android.settingslib.CustomDialogPreference;
40 
41 import java.io.File;
42 
43 /**
44  * Custom {@link DialogPreference} that allows us to editing the user name and photo.
45  */
46 public class EmergencyNamePreference extends CustomDialogPreference {
47 
48     private static final String KEY_AWAITING_RESULT = "awaiting_result";
49     private static final String KEY_SAVED_PHOTO = "pending_photo";
50 
51     private UserManager mUserManager = getContext().getSystemService(UserManager.class);
52     private EditUserPhotoController mEditUserPhotoController;
53     private Fragment mFragment;
54     private Bitmap mSavedPhoto;
55     private EditText mUserNameView;
56     private ImageView mUserPhotoView;
57     private boolean mWaitingForActivityResult = false;
58 
EmergencyNamePreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)59     public EmergencyNamePreference(Context context, AttributeSet attrs,
60             int defStyleAttr, int defStyleRes) {
61         super(context, attrs, defStyleAttr, defStyleRes);
62         setSummary(mUserManager.getUserName());
63         setIcon(getCircularUserIcon());
64         setDialogLayoutResource(R.layout.edit_user_info_dialog_content);
65     }
66 
EmergencyNamePreference(Context context, AttributeSet attrs, int defStyleAttr)67     public EmergencyNamePreference(Context context, AttributeSet attrs, int defStyleAttr) {
68         this(context, attrs, defStyleAttr, 0);
69     }
70 
EmergencyNamePreference(Context context, AttributeSet attrs)71     public EmergencyNamePreference(Context context, AttributeSet attrs) {
72         this(context, attrs, R.attr.dialogPreferenceStyle);
73     }
74 
EmergencyNamePreference(Context context)75     public EmergencyNamePreference(Context context) {
76         this(context, null);
77     }
78 
79     /**
80      * Setup fragment for Dialog and EditUserPhotoController.
81      */
setFragment(Fragment fragment)82     public void setFragment(Fragment fragment) {
83         mFragment = fragment;
84     }
85 
86     /**
87      * Reload user name and photo form UserManager.
88      */
reloadFromUserManager()89     public void reloadFromUserManager() {
90         setSummary(mUserManager.getUserName());
91         setIcon(getCircularUserIcon());
92     }
93 
94     /**
95      * Restore user photo when EditUserPhotoController had pending photo.
96      */
onRestoreInstanceState(Bundle icicle)97     public void onRestoreInstanceState(Bundle icicle) {
98         String pendingPhoto = icicle.getString(KEY_SAVED_PHOTO);
99         if (pendingPhoto != null) {
100             mSavedPhoto = EditUserPhotoController.loadNewUserPhotoBitmap(new File(pendingPhoto));
101         }
102         mWaitingForActivityResult = icicle.getBoolean(KEY_AWAITING_RESULT, false);
103     }
104 
105     /**
106      * Save a temp user photo when layout need to recreating but Dialog is showing.
107      */
onSaveInstanceState(Bundle outState)108     public void onSaveInstanceState(Bundle outState) {
109         if (getDialog() != null && getDialog().isShowing()
110                 && mEditUserPhotoController != null) {
111             // Bitmap cannot be stored into bundle because it may exceed parcel limit
112             // Store it in a temporary file instead
113             File file = mEditUserPhotoController.saveNewUserPhotoBitmap();
114             if (file != null) {
115                 outState.putString(KEY_SAVED_PHOTO, file.getPath());
116             }
117         }
118         if (mWaitingForActivityResult) {
119             outState.putBoolean(KEY_AWAITING_RESULT, mWaitingForActivityResult);
120         }
121     }
122 
123     /**
124      * Set mWaitingForActivityResult to true when EmergencyNamePreferenceDialogFragment
125      * startActivityForResult, means we are waiting the activity result.
126      */
startingActivityForResult()127     public void startingActivityForResult() {
128         mWaitingForActivityResult = true;
129     }
130 
131     /**
132      * Reset mWaitingForActivityResult and send the result code to EditUserPhotoController when
133      * EmergencyNamePreferenceDialogFragment onActivityResult.
134      */
onActivityResult(int requestCode, int resultCode, Intent data)135     public void onActivityResult(int requestCode, int resultCode, Intent data) {
136         mWaitingForActivityResult = false;
137 
138         if (getDialog() != null) {
139             mEditUserPhotoController.onActivityResult(requestCode, resultCode, data);
140         }
141     }
142 
143     @Override
onBindDialogView(View view)144     protected void onBindDialogView(View view) {
145         super.onBindDialogView(view);
146 
147         mUserNameView = view.findViewById(R.id.user_name);
148         mUserNameView.setText(mUserManager.getUserName());
149         mUserPhotoView = view.findViewById(R.id.user_photo);
150         Drawable drawable;
151         if (mSavedPhoto != null) {
152             drawable = CircleFramedDrawable.getInstance(getContext(), mSavedPhoto);
153         } else {
154             drawable = getCircularUserIcon();
155         }
156         mUserPhotoView.setImageDrawable(drawable);
157 
158         mEditUserPhotoController = createEditUserPhotoController(mUserPhotoView,
159                 getCircularUserIcon());
160     }
161 
162     @Override
onPrepareDialogBuilder(AlertDialog.Builder builder, DialogInterface.OnClickListener listener)163     protected void onPrepareDialogBuilder(AlertDialog.Builder builder,
164             DialogInterface.OnClickListener listener) {
165         super.onPrepareDialogBuilder(builder, listener);
166         builder.setTitle(R.string.name)
167                 .setCancelable(true)
168                 .setPositiveButton(android.R.string.ok, listener)
169                 .setNegativeButton(android.R.string.cancel, listener)
170                 .create();
171     }
172 
173     @Override
onDialogClosed(boolean positiveResult)174     protected void onDialogClosed(boolean positiveResult) {
175         super.onDialogClosed(positiveResult);
176         if (positiveResult) {
177             // Update the name if changed.
178             CharSequence userName = mUserNameView.getText();
179             if (!TextUtils.isEmpty(userName)) {
180                 if (mUserManager.getUserName() == null
181                         || !userName.toString().equals(mUserManager.getUserName())) {
182                     mUserManager.setUserName(UserHandle.myUserId(), userName.toString());
183                     setSummary(userName);
184                 }
185             }
186             // Update the photo if changed.
187             Drawable drawable = mEditUserPhotoController.getNewUserPhotoDrawable();
188             Bitmap bitmap = mEditUserPhotoController.getNewUserPhotoBitmap();
189             if (drawable != null && bitmap != null
190                     && !drawable.equals(getCircularUserIcon())) {
191                 new AsyncTask<Void, Void, Void>() {
192                     @Override
193                     protected Void doInBackground(Void... params) {
194                         mUserManager.setUserIcon(UserHandle.myUserId(),
195                                 mEditUserPhotoController.getNewUserPhotoBitmap());
196                         return null;
197                     }
198                 }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null);
199                 setIcon(drawable);
200             }
201             if (mFragment != null) {
202                 mFragment.getActivity().removeDialog(1);
203             }
204         }
205         clear();
206     }
207 
clear()208     private void clear() {
209         mEditUserPhotoController.removeNewUserPhotoBitmapFile();
210         mSavedPhoto = null;
211     }
212 
getCircularUserIcon()213     private Drawable getCircularUserIcon() {
214         Bitmap bitmapUserIcon = mUserManager.getUserIcon(UserHandle.myUserId());
215 
216         if (bitmapUserIcon == null) {
217             // get default user icon.
218             final Drawable defaultUserIcon = UserIcons.getDefaultUserIcon(
219                     getContext().getResources(), UserHandle.myUserId(), false);
220             bitmapUserIcon = UserIcons.convertToBitmap(defaultUserIcon);
221         }
222         Drawable drawableUserIcon = new CircleFramedDrawable(bitmapUserIcon,
223                 (int) getContext().getResources().getDimension(R.dimen.circle_avatar_size));
224 
225         return drawableUserIcon;
226     }
227 
228     @VisibleForTesting
createEditUserPhotoController(ImageView userPhotoView, Drawable drawable)229     EditUserPhotoController createEditUserPhotoController(ImageView userPhotoView,
230                 Drawable drawable) {
231         return new EditUserPhotoController(mFragment, userPhotoView,
232                 mSavedPhoto, drawable, mWaitingForActivityResult);
233     }
234 
235     public static class EmergencyNamePreferenceDialogFragment extends
236             CustomPreferenceDialogFragment {
237 
newInstance( String key)238         public static CustomDialogPreference.CustomPreferenceDialogFragment newInstance(
239                 String key) {
240             final CustomDialogPreference.CustomPreferenceDialogFragment
241                     fragment = new EmergencyNamePreferenceDialogFragment();
242             final Bundle b = new Bundle(1 /* capacity */);
243             b.putString(ARG_KEY, key);
244             fragment.setArguments(b);
245             return fragment;
246         }
247 
getEmergencyNamePreference()248         private EmergencyNamePreference getEmergencyNamePreference() {
249             return (EmergencyNamePreference) getPreference();
250         }
251 
252         @Override
onCreate(Bundle icicle)253         public void onCreate(Bundle icicle) {
254             super.onCreate(icicle);
255             getEmergencyNamePreference().setFragment(this);
256             if (icicle != null) {
257                 getEmergencyNamePreference().onRestoreInstanceState(icicle);
258             }
259         }
260 
261         @Override
onSaveInstanceState(Bundle outState)262         public void onSaveInstanceState(Bundle outState) {
263             super.onSaveInstanceState(outState);
264             getEmergencyNamePreference().onSaveInstanceState(outState);
265         }
266 
267         @Override
onActivityResult(int requestCode, int resultCode, Intent data)268         public void onActivityResult(int requestCode, int resultCode, Intent data) {
269             getEmergencyNamePreference().onActivityResult(requestCode, resultCode, data);
270         }
271 
272         @Override
startActivityForResult(Intent intent, int requestCode)273         public void startActivityForResult(Intent intent, int requestCode) {
274             getEmergencyNamePreference().startingActivityForResult();
275             super.startActivityForResult(intent, requestCode);
276         }
277     }
278 }
279