• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.content.ActivityNotFoundException;
20 import android.content.ComponentName;
21 import android.content.Context;
22 import android.content.DialogInterface;
23 import android.content.Intent;
24 import android.content.pm.PackageManager;
25 import android.content.pm.ResolveInfo;
26 import android.graphics.drawable.Drawable;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.os.Parcel;
30 import android.os.Parcelable;
31 import android.support.annotation.NonNull;
32 import android.support.annotation.Nullable;
33 import android.support.v7.preference.Preference;
34 import android.support.v7.preference.PreferenceViewHolder;
35 import android.text.BidiFormatter;
36 import android.text.TextDirectionHeuristics;
37 import android.util.AttributeSet;
38 import android.util.Log;
39 import android.view.View;
40 import android.widget.Toast;
41 
42 import com.android.emergency.CircleFramedDrawable;
43 import com.android.emergency.EmergencyContactManager;
44 import com.android.emergency.R;
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.internal.logging.MetricsLogger;
47 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
48 
49 import java.util.List;
50 
51 
52 /**
53  * A {@link Preference} to display or call a contact using the specified URI string.
54  */
55 public class ContactPreference extends Preference {
56 
57     private static final String TAG = "ContactPreference";
58 
59     static final ContactFactory DEFAULT_CONTACT_FACTORY = new ContactFactory() {
60         @Override
61         public EmergencyContactManager.Contact getContact(Context context, Uri phoneUri) {
62             return EmergencyContactManager.getContact(context, phoneUri);
63         }
64     };
65 
66     private final ContactFactory mContactFactory;
67     private EmergencyContactManager.Contact mContact;
68     @Nullable private RemoveContactPreferenceListener mRemoveContactPreferenceListener;
69     @Nullable private AlertDialog mRemoveContactDialog;
70 
71     /**
72      * Listener for removing a contact.
73      */
74     public interface RemoveContactPreferenceListener {
75         /**
76          * Callback to remove a contact preference.
77          */
onRemoveContactPreference(ContactPreference preference)78         void onRemoveContactPreference(ContactPreference preference);
79     }
80 
81     /**
82      * Interface for getting a contact for a phone number Uri.
83      */
84     public interface ContactFactory {
85         /**
86          * Gets a {@link EmergencyContactManager.Contact} for a phone {@link Uri}.
87          *
88          * @param context The context to use.
89          * @param phoneUri The phone uri.
90          * @return a contact for the given phone uri.
91          */
getContact(Context context, Uri phoneUri)92         EmergencyContactManager.Contact getContact(Context context, Uri phoneUri);
93     }
94 
ContactPreference(Context context, AttributeSet attributes)95     public ContactPreference(Context context, AttributeSet attributes) {
96         super(context, attributes);
97         mContactFactory = DEFAULT_CONTACT_FACTORY;
98     }
99 
100     /**
101      * Instantiates a ContactPreference that displays an emergency contact, taking in a Context and
102      * the Uri.
103      */
ContactPreference(Context context, @NonNull Uri phoneUri)104     public ContactPreference(Context context, @NonNull Uri phoneUri) {
105         this(context, phoneUri, DEFAULT_CONTACT_FACTORY);
106     }
107 
108     @VisibleForTesting
ContactPreference(Context context, @NonNull Uri phoneUri, @NonNull ContactFactory contactFactory)109     ContactPreference(Context context, @NonNull Uri phoneUri,
110             @NonNull ContactFactory contactFactory) {
111         super(context);
112         mContactFactory = contactFactory;
113         setOrder(DEFAULT_ORDER);
114 
115         setPhoneUri(phoneUri);
116 
117         setWidgetLayoutResource(R.layout.preference_user_delete_widget);
118         setPersistent(false);
119     }
120 
setPhoneUri(@onNull Uri phoneUri)121     public void setPhoneUri(@NonNull Uri phoneUri) {
122         if (mContact != null && !phoneUri.equals(mContact.getPhoneUri()) &&
123                 mRemoveContactDialog != null) {
124             mRemoveContactDialog.dismiss();
125         }
126         mContact = mContactFactory.getContact(getContext(), phoneUri);
127 
128         setTitle(mContact.getName());
129         setKey(mContact.getPhoneUri().toString());
130         String summary = mContact.getPhoneType() == null ?
131                 mContact.getPhoneNumber() :
132                 String.format(
133                         getContext().getResources().getString(R.string.phone_type_and_phone_number),
134                         mContact.getPhoneType(),
135                         BidiFormatter.getInstance().unicodeWrap(mContact.getPhoneNumber(),
136                                 TextDirectionHeuristics.LTR));
137         setSummary(summary);
138 
139         // Update the message to show the correct name.
140         if (mRemoveContactDialog != null) {
141             mRemoveContactDialog.setMessage(
142                     String.format(getContext().getString(R.string.remove_contact),
143                             mContact.getName()));
144         }
145 
146         //TODO: Consider doing the following in a non-UI thread.
147         Drawable icon;
148         if (mContact.getPhoto() != null) {
149             icon = new CircleFramedDrawable(mContact.getPhoto(),
150                     (int) getContext().getResources().getDimension(R.dimen.circle_avatar_size));
151         } else {
152             icon = getContext().getResources().getDrawable(R.drawable.ic_account_circle);
153         }
154         setIcon(icon);
155     }
156 
157     /** Listener to be informed when a contact preference should be deleted. */
setRemoveContactPreferenceListener( RemoveContactPreferenceListener removeContactListener)158     public void setRemoveContactPreferenceListener(
159             RemoveContactPreferenceListener removeContactListener) {
160         mRemoveContactPreferenceListener = removeContactListener;
161         if (mRemoveContactPreferenceListener == null) {
162             mRemoveContactDialog = null;
163             return;
164         }
165         if (mRemoveContactDialog != null) {
166             return;
167         }
168         // Create the remove contact dialog
169         AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
170         builder.setNegativeButton(getContext().getString(R.string.cancel), null);
171         builder.setPositiveButton(getContext().getString(R.string.remove),
172                 new DialogInterface.OnClickListener() {
173                     @Override
174                     public void onClick(DialogInterface dialogInterface,
175                                         int which) {
176                         if (mRemoveContactPreferenceListener != null) {
177                             mRemoveContactPreferenceListener
178                                     .onRemoveContactPreference(ContactPreference.this);
179                         }
180                     }
181                 });
182         builder.setMessage(String.format(getContext().getString(R.string.remove_contact),
183                 mContact.getName()));
184         mRemoveContactDialog = builder.create();
185     }
186 
187     @Override
onBindViewHolder(PreferenceViewHolder holder)188     public void onBindViewHolder(PreferenceViewHolder holder) {
189         super.onBindViewHolder(holder);
190         View deleteContactIcon = holder.findViewById(R.id.delete_contact);
191         if (mRemoveContactPreferenceListener == null) {
192             deleteContactIcon.setVisibility(View.GONE);
193         } else {
194             deleteContactIcon.setOnClickListener(new View.OnClickListener() {
195                 @Override
196                 public void onClick(View view) {
197                     showRemoveContactDialog(null);
198                 }
199             });
200 
201         }
202     }
203 
getPhoneUri()204     public Uri getPhoneUri() {
205         return mContact.getPhoneUri();
206     }
207 
208     @VisibleForTesting
getContact()209     EmergencyContactManager.Contact getContact() {
210         return mContact;
211     }
212 
213     @VisibleForTesting
getRemoveContactDialog()214     AlertDialog getRemoveContactDialog() {
215         return mRemoveContactDialog;
216     }
217 
218     /**
219      * Calls the contact.
220      */
callContact()221     public void callContact() {
222         Intent callIntent =
223                 new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + mContact.getPhoneNumber()));
224         PackageManager packageManager = getContext().getPackageManager();
225         List<ResolveInfo> infos =
226                 packageManager.queryIntentActivities(callIntent, PackageManager.MATCH_SYSTEM_ONLY);
227         if (infos == null || infos.isEmpty()) {
228             return;
229         }
230         callIntent.setComponent(new ComponentName(infos.get(0).activityInfo.packageName,
231                 infos.get(0).activityInfo.name));
232 
233         MetricsLogger.action(getContext(), MetricsEvent.ACTION_CALL_EMERGENCY_CONTACT);
234         getContext().startActivity(callIntent);
235     }
236 
237     /**
238      * Displays a contact card for the contact.
239      */
displayContact()240     public void displayContact() {
241         Intent displayIntent = new Intent(Intent.ACTION_VIEW);
242         displayIntent.setData(mContact.getContactLookupUri());
243         try {
244             getContext().startActivity(displayIntent);
245         } catch (ActivityNotFoundException e) {
246             Toast.makeText(getContext(),
247                            getContext().getString(R.string.fail_display_contact),
248                            Toast.LENGTH_LONG).show();
249             Log.w(TAG, "No contact app available to display the contact", e);
250             return;
251         }
252 
253     }
254 
255     /** Shows the dialog to remove the contact, restoring it from {@code state} if it's not null. */
showRemoveContactDialog(Bundle state)256     private void showRemoveContactDialog(Bundle state) {
257         if (mRemoveContactDialog == null) {
258             return;
259         }
260         if (state != null) {
261             mRemoveContactDialog.onRestoreInstanceState(state);
262         }
263         mRemoveContactDialog.show();
264     }
265 
266     @Override
onSaveInstanceState()267     protected Parcelable onSaveInstanceState() {
268         final Parcelable superState = super.onSaveInstanceState();
269         if (mRemoveContactDialog == null || !mRemoveContactDialog.isShowing()) {
270             return superState;
271         }
272         final SavedState myState = new SavedState(superState);
273         myState.isDialogShowing = true;
274         myState.dialogBundle = mRemoveContactDialog.onSaveInstanceState();
275         return myState;
276     }
277 
278     @Override
onRestoreInstanceState(Parcelable state)279     protected void onRestoreInstanceState(Parcelable state) {
280         if (state == null || !state.getClass().equals(SavedState.class)) {
281             // Didn't save state for us in onSaveInstanceState
282             super.onRestoreInstanceState(state);
283             return;
284         }
285         SavedState myState = (SavedState) state;
286         super.onRestoreInstanceState(myState.getSuperState());
287         if (myState.isDialogShowing) {
288             showRemoveContactDialog(myState.dialogBundle);
289         }
290     }
291 
292     private static class SavedState extends BaseSavedState {
293         boolean isDialogShowing;
294         Bundle dialogBundle;
295 
SavedState(Parcel source)296         public SavedState(Parcel source) {
297             super(source);
298             isDialogShowing = source.readInt() == 1;
299             dialogBundle = source.readBundle();
300         }
301 
302         @Override
writeToParcel(Parcel dest, int flags)303         public void writeToParcel(Parcel dest, int flags) {
304             super.writeToParcel(dest, flags);
305             dest.writeInt(isDialogShowing ? 1 : 0);
306             dest.writeBundle(dialogBundle);
307         }
308 
SavedState(Parcelable superState)309         public SavedState(Parcelable superState) {
310             super(superState);
311         }
312 
313         public static final Parcelable.Creator<SavedState> CREATOR =
314                 new Parcelable.Creator<SavedState>() {
315                     public SavedState createFromParcel(Parcel in) {
316                         return new SavedState(in);
317                     }
318 
319                     public SavedState[] newArray(int size) {
320                         return new SavedState[size];
321                     }
322                 };
323     }
324 }
325