blob: 7194b5e0572988e0d90462af8223522b9dbf1b6d [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;
Akshay Kannancdb6c142016-01-20 18:25:44 -080031import android.preference.Preference;
mariagpuyolaee23782016-02-16 13:29:49 -080032import android.support.annotation.NonNull;
mariagpuyol16472ca2016-02-16 17:49:26 -080033import android.support.annotation.Nullable;
mariagpuyol9bc08182016-05-13 14:54:49 -070034import android.text.BidiFormatter;
35import android.text.TextDirectionHeuristics;
mariagpuyol27b306e2017-03-01 13:14:31 -080036import android.util.Log;
mariagpuyold19ace52016-02-12 10:37:26 -080037import android.view.View;
mariagpuyol27b306e2017-03-01 13:14:31 -080038import android.widget.Toast;
mariagpuyold19ace52016-02-12 10:37:26 -080039
mariagpuyolacef32a2016-12-12 11:33:20 -080040import com.android.emergency.CircleFramedDrawable;
mariagpuyol58748352016-03-09 15:36:28 -080041import com.android.emergency.EmergencyContactManager;
mariagpuyol58748352016-03-09 15:36:28 -080042import com.android.emergency.R;
mariagpuyol9e2eea12016-04-01 18:10:12 -070043import com.android.internal.annotations.VisibleForTesting;
Akshay Kannan44ef39d2016-01-26 09:08:41 -080044import com.android.internal.logging.MetricsLogger;
Tamas Berghammer2fdeb9a2016-06-22 15:28:50 +010045import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Akshay Kannan44ef39d2016-01-26 09:08:41 -080046
mariagpuyole0f631b2016-08-16 18:58:26 -070047import java.util.List;
48
Akshay Kannancdb6c142016-01-20 18:25:44 -080049
50/**
mariagpuyol5cf7f732016-03-08 09:48:15 -080051 * A {@link Preference} to display or call a contact using the specified URI string.
Akshay Kannancdb6c142016-01-20 18:25:44 -080052 */
53public class ContactPreference extends Preference {
54
mariagpuyol27b306e2017-03-01 13:14:31 -080055 private static final String TAG = "ContactPreference";
56
Juan Lang6ad3b5f2017-06-08 17:14:27 -070057 static final ContactFactory DEFAULT_CONTACT_FACTORY = new ContactFactory() {
58 @Override
59 public EmergencyContactManager.Contact getContact(Context context, Uri phoneUri) {
60 return EmergencyContactManager.getContact(context, phoneUri);
61 }
62 };
63
Juan Lang95113882017-05-30 15:20:50 -070064 private final ContactFactory mContactFactory;
mariagpuyol53745bf2016-03-23 14:43:55 -070065 private EmergencyContactManager.Contact mContact;
mariagpuyol609f68a2016-02-22 17:45:47 -080066 @Nullable private RemoveContactPreferenceListener mRemoveContactPreferenceListener;
mariagpuyol53745bf2016-03-23 14:43:55 -070067 @Nullable private AlertDialog mRemoveContactDialog;
mariagpuyold19ace52016-02-12 10:37:26 -080068
69 /**
mariagpuyol609f68a2016-02-22 17:45:47 -080070 * Listener for removing a contact.
mariagpuyold19ace52016-02-12 10:37:26 -080071 */
mariagpuyol609f68a2016-02-22 17:45:47 -080072 public interface RemoveContactPreferenceListener {
mariagpuyold19ace52016-02-12 10:37:26 -080073 /**
mariagpuyol609f68a2016-02-22 17:45:47 -080074 * Callback to remove a contact preference.
mariagpuyold19ace52016-02-12 10:37:26 -080075 */
mariagpuyol609f68a2016-02-22 17:45:47 -080076 void onRemoveContactPreference(ContactPreference preference);
mariagpuyold19ace52016-02-12 10:37:26 -080077 }
Akshay Kannancdb6c142016-01-20 18:25:44 -080078
79 /**
Juan Lang95113882017-05-30 15:20:50 -070080 * Interface for getting a contact for a phone number Uri.
81 */
82 public interface ContactFactory {
83 /**
84 * Gets a {@link EmergencyContactManager.Contact} for a phone {@link Uri}.
85 *
86 * @param context The context to use.
87 * @param phoneUri The phone uri.
88 * @return a contact for the given phone uri.
89 */
90 EmergencyContactManager.Contact getContact(Context context, Uri phoneUri);
91 }
92
93 /**
Akshay Kannancdb6c142016-01-20 18:25:44 -080094 * Instantiates a ContactPreference that displays an emergency contact, taking in a Context and
mariagpuyol5cf7f732016-03-08 09:48:15 -080095 * the Uri.
Akshay Kannancdb6c142016-01-20 18:25:44 -080096 */
mariagpuyol08e2e512017-03-08 10:00:15 -080097 public ContactPreference(Context context, @NonNull Uri phoneUri) {
Juan Lang6ad3b5f2017-06-08 17:14:27 -070098 this(context, phoneUri, DEFAULT_CONTACT_FACTORY);
Juan Lang95113882017-05-30 15:20:50 -070099 }
100
101 @VisibleForTesting
Juan Lang6ad3b5f2017-06-08 17:14:27 -0700102 ContactPreference(Context context, @NonNull Uri phoneUri,
Juan Lang95113882017-05-30 15:20:50 -0700103 @NonNull ContactFactory contactFactory) {
Akshay Kannancdb6c142016-01-20 18:25:44 -0800104 super(context);
Juan Lang95113882017-05-30 15:20:50 -0700105 mContactFactory = contactFactory;
mariagpuyol609f68a2016-02-22 17:45:47 -0800106 setOrder(DEFAULT_ORDER);
mariagpuyol53745bf2016-03-23 14:43:55 -0700107
mariagpuyol08e2e512017-03-08 10:00:15 -0800108 setPhoneUri(phoneUri);
mariagpuyol53745bf2016-03-23 14:43:55 -0700109
110 setWidgetLayoutResource(R.layout.preference_user_delete_widget);
111 setPersistent(false);
112 }
113
mariagpuyol08e2e512017-03-08 10:00:15 -0800114 public void setPhoneUri(@NonNull Uri phoneUri) {
115 if (mContact != null && !phoneUri.equals(mContact.getPhoneUri()) &&
mariagpuyol53745bf2016-03-23 14:43:55 -0700116 mRemoveContactDialog != null) {
117 mRemoveContactDialog.dismiss();
118 }
Juan Lang95113882017-05-30 15:20:50 -0700119 mContact = mContactFactory.getContact(getContext(), phoneUri);
mariagpuyol53745bf2016-03-23 14:43:55 -0700120
mariagpuyol5cf7f732016-03-08 09:48:15 -0800121 setTitle(mContact.getName());
mariagpuyol08e2e512017-03-08 10:00:15 -0800122 setKey(mContact.getPhoneUri().toString());
mariagpuyolf9ab89a2016-03-10 09:19:07 -0800123 String summary = mContact.getPhoneType() == null ?
124 mContact.getPhoneNumber() :
125 String.format(
mariagpuyol53745bf2016-03-23 14:43:55 -0700126 getContext().getResources().getString(R.string.phone_type_and_phone_number),
mariagpuyolf9ab89a2016-03-10 09:19:07 -0800127 mContact.getPhoneType(),
mariagpuyol9bc08182016-05-13 14:54:49 -0700128 BidiFormatter.getInstance().unicodeWrap(mContact.getPhoneNumber(),
129 TextDirectionHeuristics.LTR));
mariagpuyolf9ab89a2016-03-10 09:19:07 -0800130 setSummary(summary);
mariagpuyol53745bf2016-03-23 14:43:55 -0700131
132 // Update the message to show the correct name.
133 if (mRemoveContactDialog != null) {
134 mRemoveContactDialog.setMessage(
135 String.format(getContext().getString(R.string.remove_contact),
136 mContact.getName()));
137 }
mariagpuyol95dc0402016-02-17 11:12:46 -0800138
139 //TODO: Consider doing the following in a non-UI thread.
mariagpuyol9be32652016-03-16 12:34:53 -0700140 Drawable icon;
141 if (mContact.getPhoto() != null) {
142 icon = new CircleFramedDrawable(mContact.getPhoto(),
mariagpuyol53745bf2016-03-23 14:43:55 -0700143 (int) getContext().getResources().getDimension(R.dimen.circle_avatar_size));
mariagpuyol9be32652016-03-16 12:34:53 -0700144 } else {
mariagpuyol53745bf2016-03-23 14:43:55 -0700145 icon = getContext().getResources().getDrawable(R.drawable.ic_person_black_24dp);
mariagpuyol9be32652016-03-16 12:34:53 -0700146 }
mariagpuyol95dc0402016-02-17 11:12:46 -0800147 setIcon(icon);
148 }
149
mariagpuyol609f68a2016-02-22 17:45:47 -0800150 /** Listener to be informed when a contact preference should be deleted. */
151 public void setRemoveContactPreferenceListener(
152 RemoveContactPreferenceListener removeContactListener) {
153 mRemoveContactPreferenceListener = removeContactListener;
mariagpuyol53745bf2016-03-23 14:43:55 -0700154 if (mRemoveContactPreferenceListener == null) {
155 mRemoveContactDialog = null;
156 return;
157 }
158 if (mRemoveContactDialog != null) {
159 return;
160 }
161 // Create the remove contact dialog
162 AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
163 builder.setNegativeButton(getContext().getString(R.string.cancel), null);
164 builder.setPositiveButton(getContext().getString(R.string.remove),
165 new DialogInterface.OnClickListener() {
166 @Override
167 public void onClick(DialogInterface dialogInterface,
168 int which) {
169 if (mRemoveContactPreferenceListener != null) {
170 mRemoveContactPreferenceListener
171 .onRemoveContactPreference(ContactPreference.this);
172 }
173 }
174 });
175 builder.setMessage(String.format(getContext().getString(R.string.remove_contact),
176 mContact.getName()));
177 mRemoveContactDialog = builder.create();
mariagpuyol609f68a2016-02-22 17:45:47 -0800178 }
179
mariagpuyold19ace52016-02-12 10:37:26 -0800180 @Override
181 protected void onBindView(View view) {
182 super.onBindView(view);
183 View deleteContactIcon = view.findViewById(R.id.delete_contact);
mariagpuyol609f68a2016-02-22 17:45:47 -0800184 if (mRemoveContactPreferenceListener == null) {
mariagpuyol16472ca2016-02-16 17:49:26 -0800185 deleteContactIcon.setVisibility(View.GONE);
186 } else {
mariagpuyol609f68a2016-02-22 17:45:47 -0800187 deleteContactIcon.setOnClickListener(new View.OnClickListener() {
188 @Override
189 public void onClick(View view) {
mariagpuyol53745bf2016-03-23 14:43:55 -0700190 showRemoveContactDialog(null);
mariagpuyol609f68a2016-02-22 17:45:47 -0800191 }
192 });
193
mariagpuyold19ace52016-02-12 10:37:26 -0800194 }
Akshay Kannancdb6c142016-01-20 18:25:44 -0800195 }
196
mariagpuyol08e2e512017-03-08 10:00:15 -0800197 public Uri getPhoneUri() {
198 return mContact.getPhoneUri();
mariagpuyol609f68a2016-02-22 17:45:47 -0800199 }
200
mariagpuyol9e2eea12016-04-01 18:10:12 -0700201 @VisibleForTesting
202 EmergencyContactManager.Contact getContact() {
203 return mContact;
204 }
205
206 @VisibleForTesting
207 AlertDialog getRemoveContactDialog() {
208 return mRemoveContactDialog;
209 }
210
Akshay Kannancdb6c142016-01-20 18:25:44 -0800211 /**
Akshay Kannan44ef39d2016-01-26 09:08:41 -0800212 * Calls the contact.
213 */
mariagpuyol5cf7f732016-03-08 09:48:15 -0800214 public void callContact() {
215 Intent callIntent =
216 new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + mContact.getPhoneNumber()));
mariagpuyole0f631b2016-08-16 18:58:26 -0700217 PackageManager packageManager = getContext().getPackageManager();
218 List<ResolveInfo> infos =
219 packageManager.queryIntentActivities(callIntent, PackageManager.MATCH_SYSTEM_ONLY);
220 if (infos == null || infos.isEmpty()) {
221 return;
222 }
223 callIntent.setComponent(new ComponentName(infos.get(0).activityInfo.packageName,
224 infos.get(0).activityInfo.name));
225
mariagpuyol5cf7f732016-03-08 09:48:15 -0800226 MetricsLogger.action(getContext(), MetricsEvent.ACTION_CALL_EMERGENCY_CONTACT);
227 getContext().startActivity(callIntent);
mariagpuyol0504a442016-02-17 16:22:57 -0800228 }
mariagpuyol36b3f9d2016-03-11 16:02:31 -0800229
230 /**
231 * Displays a contact card for the contact.
232 */
233 public void displayContact() {
mariagpuyol27b306e2017-03-01 13:14:31 -0800234 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
mariagpuyol36b3f9d2016-03-11 16:02:31 -0800246 }
mariagpuyol53745bf2016-03-23 14:43:55 -0700247
248 /** Shows the dialog to remove the contact, restoring it from {@code state} if it's not null. */
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
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
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
289 public SavedState(Parcel source) {
290 super(source);
291 isDialogShowing = source.readInt() == 1;
292 dialogBundle = source.readBundle();
293 }
294
295 @Override
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
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 }
mariagpuyole0f631b2016-08-16 18:58:26 -0700317}