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