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 */ 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 */ 92 EmergencyContactManager.Contact getContact(Context context, Uri phoneUri); 93 } 94 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 */ 104 public ContactPreference(Context context, @NonNull Uri phoneUri) { 105 this(context, phoneUri, DEFAULT_CONTACT_FACTORY); 106 } 107 108 @VisibleForTesting 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 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. */ 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 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 204 public Uri getPhoneUri() { 205 return mContact.getPhoneUri(); 206 } 207 208 @VisibleForTesting 209 EmergencyContactManager.Contact getContact() { 210 return mContact; 211 } 212 213 @VisibleForTesting 214 AlertDialog getRemoveContactDialog() { 215 return mRemoveContactDialog; 216 } 217 218 /** 219 * Calls the contact. 220 */ 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 */ 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. */ 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 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 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 296 public SavedState(Parcel source) { 297 super(source); 298 isDialogShowing = source.readInt() == 1; 299 dialogBundle = source.readBundle(); 300 } 301 302 @Override 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 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