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