blob: a543ae0d8fe2d910d75c7851007a5915bdf179d5 [file] [log] [blame]
Akshay Kannancdb6c142016-01-20 18:25:44 -08001/*
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 */
mariagpuyol58748352016-03-09 15:36:28 -080016package com.android.emergency.preferences;
Akshay Kannancdb6c142016-01-20 18:25:44 -080017
Akshay Kannan44ef39d2016-01-26 09:08:41 -080018import android.app.AlertDialog;
mariagpuyol27b306e2017-03-01 13:14:31 -080019import android.content.ActivityNotFoundException;
mariagpuyole0f631b2016-08-16 18:58:26 -070020import android.content.ComponentName;
Akshay Kannancdb6c142016-01-20 18:25:44 -080021import android.content.Context;
mariagpuyold19ace52016-02-12 10:37:26 -080022import android.content.DialogInterface;
Akshay Kannancdb6c142016-01-20 18:25:44 -080023import android.content.Intent;
mariagpuyole0f631b2016-08-16 18:58:26 -070024import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
mariagpuyol95dc0402016-02-17 11:12:46 -080026import android.graphics.drawable.Drawable;
Akshay Kannancdb6c142016-01-20 18:25:44 -080027import android.net.Uri;
mariagpuyol53745bf2016-03-23 14:43:55 -070028import android.os.Bundle;
29import android.os.Parcel;
30import android.os.Parcelable;
mariagpuyolaee23782016-02-16 13:29:49 -080031import android.support.annotation.NonNull;
mariagpuyol16472ca2016-02-16 17:49:26 -080032import android.support.annotation.Nullable;
Juan Lang2c0518a2017-05-10 17:00:32 -070033import android.support.v7.preference.Preference;
34import android.support.v7.preference.PreferenceViewHolder;
mariagpuyol9bc08182016-05-13 14:54:49 -070035import android.text.BidiFormatter;
36import android.text.TextDirectionHeuristics;
Juan Lang2c0518a2017-05-10 17:00:32 -070037import android.util.AttributeSet;
mariagpuyol27b306e2017-03-01 13:14:31 -080038import android.util.Log;
mariagpuyold19ace52016-02-12 10:37:26 -080039import android.view.View;
mariagpuyol27b306e2017-03-01 13:14:31 -080040import android.widget.Toast;
mariagpuyold19ace52016-02-12 10:37:26 -080041
mariagpuyolacef32a2016-12-12 11:33:20 -080042import com.android.emergency.CircleFramedDrawable;
mariagpuyol58748352016-03-09 15:36:28 -080043import com.android.emergency.EmergencyContactManager;
mariagpuyol58748352016-03-09 15:36:28 -080044import com.android.emergency.R;
mariagpuyol9e2eea12016-04-01 18:10:12 -070045import com.android.internal.annotations.VisibleForTesting;
Akshay Kannan44ef39d2016-01-26 09:08:41 -080046import com.android.internal.logging.MetricsLogger;
Tamas Berghammer2fdeb9a2016-06-22 15:28:50 +010047import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Akshay Kannan44ef39d2016-01-26 09:08:41 -080048
mariagpuyole0f631b2016-08-16 18:58:26 -070049import java.util.List;
50
Akshay Kannancdb6c142016-01-20 18:25:44 -080051
52/**
mariagpuyol5cf7f732016-03-08 09:48:15 -080053 * A {@link Preference} to display or call a contact using the specified URI string.
Akshay Kannancdb6c142016-01-20 18:25:44 -080054 */
55public class ContactPreference extends Preference {
56
mariagpuyol27b306e2017-03-01 13:14:31 -080057 private static final String TAG = "ContactPreference";
58
Juan Lang6ad3b5f2017-06-08 17:14:27 -070059 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
Juan Lang95113882017-05-30 15:20:50 -070066 private final ContactFactory mContactFactory;
mariagpuyol53745bf2016-03-23 14:43:55 -070067 private EmergencyContactManager.Contact mContact;
mariagpuyol609f68a2016-02-22 17:45:47 -080068 @Nullable private RemoveContactPreferenceListener mRemoveContactPreferenceListener;
mariagpuyol53745bf2016-03-23 14:43:55 -070069 @Nullable private AlertDialog mRemoveContactDialog;
mariagpuyold19ace52016-02-12 10:37:26 -080070
71 /**
mariagpuyol609f68a2016-02-22 17:45:47 -080072 * Listener for removing a contact.
mariagpuyold19ace52016-02-12 10:37:26 -080073 */
mariagpuyol609f68a2016-02-22 17:45:47 -080074 public interface RemoveContactPreferenceListener {
mariagpuyold19ace52016-02-12 10:37:26 -080075 /**
mariagpuyol609f68a2016-02-22 17:45:47 -080076 * Callback to remove a contact preference.
mariagpuyold19ace52016-02-12 10:37:26 -080077 */
mariagpuyol609f68a2016-02-22 17:45:47 -080078 void onRemoveContactPreference(ContactPreference preference);
mariagpuyold19ace52016-02-12 10:37:26 -080079 }
Akshay Kannancdb6c142016-01-20 18:25:44 -080080
81 /**
Juan Lang95113882017-05-30 15:20:50 -070082 * 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
Juan Lang2c0518a2017-05-10 17:00:32 -070095 public ContactPreference(Context context, AttributeSet attributes) {
96 super(context, attributes);
97 mContactFactory = DEFAULT_CONTACT_FACTORY;
98 }
99
Juan Lang95113882017-05-30 15:20:50 -0700100 /**
Akshay Kannancdb6c142016-01-20 18:25:44 -0800101 * Instantiates a ContactPreference that displays an emergency contact, taking in a Context and
mariagpuyol5cf7f732016-03-08 09:48:15 -0800102 * the Uri.
Akshay Kannancdb6c142016-01-20 18:25:44 -0800103 */
mariagpuyol08e2e512017-03-08 10:00:15 -0800104 public ContactPreference(Context context, @NonNull Uri phoneUri) {
Juan Lang6ad3b5f2017-06-08 17:14:27 -0700105 this(context, phoneUri, DEFAULT_CONTACT_FACTORY);
Juan Lang95113882017-05-30 15:20:50 -0700106 }
107
108 @VisibleForTesting
Juan Lang6ad3b5f2017-06-08 17:14:27 -0700109 ContactPreference(Context context, @NonNull Uri phoneUri,
Juan Lang95113882017-05-30 15:20:50 -0700110 @NonNull ContactFactory contactFactory) {
Akshay Kannancdb6c142016-01-20 18:25:44 -0800111 super(context);
Juan Lang95113882017-05-30 15:20:50 -0700112 mContactFactory = contactFactory;
mariagpuyol609f68a2016-02-22 17:45:47 -0800113 setOrder(DEFAULT_ORDER);
mariagpuyol53745bf2016-03-23 14:43:55 -0700114
mariagpuyol08e2e512017-03-08 10:00:15 -0800115 setPhoneUri(phoneUri);
mariagpuyol53745bf2016-03-23 14:43:55 -0700116
117 setWidgetLayoutResource(R.layout.preference_user_delete_widget);
118 setPersistent(false);
119 }
120
mariagpuyol08e2e512017-03-08 10:00:15 -0800121 public void setPhoneUri(@NonNull Uri phoneUri) {
122 if (mContact != null && !phoneUri.equals(mContact.getPhoneUri()) &&
mariagpuyol53745bf2016-03-23 14:43:55 -0700123 mRemoveContactDialog != null) {
124 mRemoveContactDialog.dismiss();
125 }
Juan Lang95113882017-05-30 15:20:50 -0700126 mContact = mContactFactory.getContact(getContext(), phoneUri);
mariagpuyol53745bf2016-03-23 14:43:55 -0700127
mariagpuyol5cf7f732016-03-08 09:48:15 -0800128 setTitle(mContact.getName());
mariagpuyol08e2e512017-03-08 10:00:15 -0800129 setKey(mContact.getPhoneUri().toString());
mariagpuyolf9ab89a2016-03-10 09:19:07 -0800130 String summary = mContact.getPhoneType() == null ?
131 mContact.getPhoneNumber() :
132 String.format(
mariagpuyol53745bf2016-03-23 14:43:55 -0700133 getContext().getResources().getString(R.string.phone_type_and_phone_number),
mariagpuyolf9ab89a2016-03-10 09:19:07 -0800134 mContact.getPhoneType(),
mariagpuyol9bc08182016-05-13 14:54:49 -0700135 BidiFormatter.getInstance().unicodeWrap(mContact.getPhoneNumber(),
136 TextDirectionHeuristics.LTR));
mariagpuyolf9ab89a2016-03-10 09:19:07 -0800137 setSummary(summary);
mariagpuyol53745bf2016-03-23 14:43:55 -0700138
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 }
mariagpuyol95dc0402016-02-17 11:12:46 -0800145
146 //TODO: Consider doing the following in a non-UI thread.
mariagpuyol9be32652016-03-16 12:34:53 -0700147 Drawable icon;
148 if (mContact.getPhoto() != null) {
149 icon = new CircleFramedDrawable(mContact.getPhoto(),
mariagpuyol53745bf2016-03-23 14:43:55 -0700150 (int) getContext().getResources().getDimension(R.dimen.circle_avatar_size));
mariagpuyol9be32652016-03-16 12:34:53 -0700151 } else {
mariagpuyol53745bf2016-03-23 14:43:55 -0700152 icon = getContext().getResources().getDrawable(R.drawable.ic_person_black_24dp);
mariagpuyol9be32652016-03-16 12:34:53 -0700153 }
mariagpuyol95dc0402016-02-17 11:12:46 -0800154 setIcon(icon);
155 }
156
mariagpuyol609f68a2016-02-22 17:45:47 -0800157 /** Listener to be informed when a contact preference should be deleted. */
158 public void setRemoveContactPreferenceListener(
159 RemoveContactPreferenceListener removeContactListener) {
160 mRemoveContactPreferenceListener = removeContactListener;
mariagpuyol53745bf2016-03-23 14:43:55 -0700161 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();
mariagpuyol609f68a2016-02-22 17:45:47 -0800185 }
186
mariagpuyold19ace52016-02-12 10:37:26 -0800187 @Override
Juan Lang2c0518a2017-05-10 17:00:32 -0700188 public void onBindViewHolder(PreferenceViewHolder holder) {
189 super.onBindViewHolder(holder);
190 View deleteContactIcon = holder.findViewById(R.id.delete_contact);
mariagpuyol609f68a2016-02-22 17:45:47 -0800191 if (mRemoveContactPreferenceListener == null) {
mariagpuyol16472ca2016-02-16 17:49:26 -0800192 deleteContactIcon.setVisibility(View.GONE);
193 } else {
mariagpuyol609f68a2016-02-22 17:45:47 -0800194 deleteContactIcon.setOnClickListener(new View.OnClickListener() {
195 @Override
196 public void onClick(View view) {
mariagpuyol53745bf2016-03-23 14:43:55 -0700197 showRemoveContactDialog(null);
mariagpuyol609f68a2016-02-22 17:45:47 -0800198 }
199 });
200
mariagpuyold19ace52016-02-12 10:37:26 -0800201 }
Akshay Kannancdb6c142016-01-20 18:25:44 -0800202 }
203
mariagpuyol08e2e512017-03-08 10:00:15 -0800204 public Uri getPhoneUri() {
205 return mContact.getPhoneUri();
mariagpuyol609f68a2016-02-22 17:45:47 -0800206 }
207
mariagpuyol9e2eea12016-04-01 18:10:12 -0700208 @VisibleForTesting
209 EmergencyContactManager.Contact getContact() {
210 return mContact;
211 }
212
213 @VisibleForTesting
214 AlertDialog getRemoveContactDialog() {
215 return mRemoveContactDialog;
216 }
217
Akshay Kannancdb6c142016-01-20 18:25:44 -0800218 /**
Akshay Kannan44ef39d2016-01-26 09:08:41 -0800219 * Calls the contact.
220 */
mariagpuyol5cf7f732016-03-08 09:48:15 -0800221 public void callContact() {
222 Intent callIntent =
223 new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + mContact.getPhoneNumber()));
mariagpuyole0f631b2016-08-16 18:58:26 -0700224 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
mariagpuyol5cf7f732016-03-08 09:48:15 -0800233 MetricsLogger.action(getContext(), MetricsEvent.ACTION_CALL_EMERGENCY_CONTACT);
234 getContext().startActivity(callIntent);
mariagpuyol0504a442016-02-17 16:22:57 -0800235 }
mariagpuyol36b3f9d2016-03-11 16:02:31 -0800236
237 /**
238 * Displays a contact card for the contact.
239 */
240 public void displayContact() {
mariagpuyol27b306e2017-03-01 13:14:31 -0800241 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
mariagpuyol36b3f9d2016-03-11 16:02:31 -0800253 }
mariagpuyol53745bf2016-03-23 14:43:55 -0700254
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 }
mariagpuyole0f631b2016-08-16 18:58:26 -0700324}