blob: ff5e3cc669d8454828c0cd0980be60102f275d90 [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;
Tyler Gunn1c888f82018-04-15 22:49:59 -070035import android.telecom.TelecomManager;
mariagpuyol9bc08182016-05-13 14:54:49 -070036import android.text.BidiFormatter;
37import android.text.TextDirectionHeuristics;
Juan Lang2c0518a2017-05-10 17:00:32 -070038import android.util.AttributeSet;
mariagpuyol27b306e2017-03-01 13:14:31 -080039import android.util.Log;
mariagpuyold19ace52016-02-12 10:37:26 -080040import android.view.View;
mariagpuyol27b306e2017-03-01 13:14:31 -080041import android.widget.Toast;
mariagpuyold19ace52016-02-12 10:37:26 -080042
mariagpuyolacef32a2016-12-12 11:33:20 -080043import com.android.emergency.CircleFramedDrawable;
mariagpuyol58748352016-03-09 15:36:28 -080044import com.android.emergency.EmergencyContactManager;
mariagpuyol58748352016-03-09 15:36:28 -080045import com.android.emergency.R;
mariagpuyol9e2eea12016-04-01 18:10:12 -070046import com.android.internal.annotations.VisibleForTesting;
Akshay Kannan44ef39d2016-01-26 09:08:41 -080047import com.android.internal.logging.MetricsLogger;
Tamas Berghammer2fdeb9a2016-06-22 15:28:50 +010048import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
Akshay Kannan44ef39d2016-01-26 09:08:41 -080049
mariagpuyole0f631b2016-08-16 18:58:26 -070050import java.util.List;
51
Akshay Kannancdb6c142016-01-20 18:25:44 -080052
53/**
mariagpuyol5cf7f732016-03-08 09:48:15 -080054 * A {@link Preference} to display or call a contact using the specified URI string.
Akshay Kannancdb6c142016-01-20 18:25:44 -080055 */
56public class ContactPreference extends Preference {
57
mariagpuyol27b306e2017-03-01 13:14:31 -080058 private static final String TAG = "ContactPreference";
59
Juan Lang6ad3b5f2017-06-08 17:14:27 -070060 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
Juan Lang95113882017-05-30 15:20:50 -070067 private final ContactFactory mContactFactory;
mariagpuyol53745bf2016-03-23 14:43:55 -070068 private EmergencyContactManager.Contact mContact;
mariagpuyol609f68a2016-02-22 17:45:47 -080069 @Nullable private RemoveContactPreferenceListener mRemoveContactPreferenceListener;
mariagpuyol53745bf2016-03-23 14:43:55 -070070 @Nullable private AlertDialog mRemoveContactDialog;
mariagpuyold19ace52016-02-12 10:37:26 -080071
72 /**
mariagpuyol609f68a2016-02-22 17:45:47 -080073 * Listener for removing a contact.
mariagpuyold19ace52016-02-12 10:37:26 -080074 */
mariagpuyol609f68a2016-02-22 17:45:47 -080075 public interface RemoveContactPreferenceListener {
mariagpuyold19ace52016-02-12 10:37:26 -080076 /**
mariagpuyol609f68a2016-02-22 17:45:47 -080077 * Callback to remove a contact preference.
mariagpuyold19ace52016-02-12 10:37:26 -080078 */
mariagpuyol609f68a2016-02-22 17:45:47 -080079 void onRemoveContactPreference(ContactPreference preference);
mariagpuyold19ace52016-02-12 10:37:26 -080080 }
Akshay Kannancdb6c142016-01-20 18:25:44 -080081
82 /**
Juan Lang95113882017-05-30 15:20:50 -070083 * 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 */
93 EmergencyContactManager.Contact getContact(Context context, Uri phoneUri);
94 }
95
Juan Lang2c0518a2017-05-10 17:00:32 -070096 public ContactPreference(Context context, AttributeSet attributes) {
97 super(context, attributes);
98 mContactFactory = DEFAULT_CONTACT_FACTORY;
99 }
100
Juan Lang95113882017-05-30 15:20:50 -0700101 /**
Akshay Kannancdb6c142016-01-20 18:25:44 -0800102 * Instantiates a ContactPreference that displays an emergency contact, taking in a Context and
mariagpuyol5cf7f732016-03-08 09:48:15 -0800103 * the Uri.
Akshay Kannancdb6c142016-01-20 18:25:44 -0800104 */
mariagpuyol08e2e512017-03-08 10:00:15 -0800105 public ContactPreference(Context context, @NonNull Uri phoneUri) {
Juan Lang6ad3b5f2017-06-08 17:14:27 -0700106 this(context, phoneUri, DEFAULT_CONTACT_FACTORY);
Juan Lang95113882017-05-30 15:20:50 -0700107 }
108
109 @VisibleForTesting
Juan Lang6ad3b5f2017-06-08 17:14:27 -0700110 ContactPreference(Context context, @NonNull Uri phoneUri,
Juan Lang95113882017-05-30 15:20:50 -0700111 @NonNull ContactFactory contactFactory) {
Akshay Kannancdb6c142016-01-20 18:25:44 -0800112 super(context);
Juan Lang95113882017-05-30 15:20:50 -0700113 mContactFactory = contactFactory;
mariagpuyol609f68a2016-02-22 17:45:47 -0800114 setOrder(DEFAULT_ORDER);
mariagpuyol53745bf2016-03-23 14:43:55 -0700115
mariagpuyol08e2e512017-03-08 10:00:15 -0800116 setPhoneUri(phoneUri);
mariagpuyol53745bf2016-03-23 14:43:55 -0700117
118 setWidgetLayoutResource(R.layout.preference_user_delete_widget);
119 setPersistent(false);
120 }
121
mariagpuyol08e2e512017-03-08 10:00:15 -0800122 public void setPhoneUri(@NonNull Uri phoneUri) {
123 if (mContact != null && !phoneUri.equals(mContact.getPhoneUri()) &&
mariagpuyol53745bf2016-03-23 14:43:55 -0700124 mRemoveContactDialog != null) {
125 mRemoveContactDialog.dismiss();
126 }
Juan Lang95113882017-05-30 15:20:50 -0700127 mContact = mContactFactory.getContact(getContext(), phoneUri);
mariagpuyol53745bf2016-03-23 14:43:55 -0700128
mariagpuyol5cf7f732016-03-08 09:48:15 -0800129 setTitle(mContact.getName());
mariagpuyol08e2e512017-03-08 10:00:15 -0800130 setKey(mContact.getPhoneUri().toString());
mariagpuyolf9ab89a2016-03-10 09:19:07 -0800131 String summary = mContact.getPhoneType() == null ?
132 mContact.getPhoneNumber() :
133 String.format(
mariagpuyol53745bf2016-03-23 14:43:55 -0700134 getContext().getResources().getString(R.string.phone_type_and_phone_number),
mariagpuyolf9ab89a2016-03-10 09:19:07 -0800135 mContact.getPhoneType(),
mariagpuyol9bc08182016-05-13 14:54:49 -0700136 BidiFormatter.getInstance().unicodeWrap(mContact.getPhoneNumber(),
137 TextDirectionHeuristics.LTR));
mariagpuyolf9ab89a2016-03-10 09:19:07 -0800138 setSummary(summary);
mariagpuyol53745bf2016-03-23 14:43:55 -0700139
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 }
mariagpuyol95dc0402016-02-17 11:12:46 -0800146
147 //TODO: Consider doing the following in a non-UI thread.
mariagpuyol9be32652016-03-16 12:34:53 -0700148 Drawable icon;
149 if (mContact.getPhoto() != null) {
150 icon = new CircleFramedDrawable(mContact.getPhoto(),
mariagpuyol53745bf2016-03-23 14:43:55 -0700151 (int) getContext().getResources().getDimension(R.dimen.circle_avatar_size));
mariagpuyol9be32652016-03-16 12:34:53 -0700152 } else {
Juan Lang112800e2017-09-07 15:28:26 -0700153 icon = getContext().getResources().getDrawable(R.drawable.ic_account_circle);
mariagpuyol9be32652016-03-16 12:34:53 -0700154 }
mariagpuyol95dc0402016-02-17 11:12:46 -0800155 setIcon(icon);
156 }
157
mariagpuyol609f68a2016-02-22 17:45:47 -0800158 /** Listener to be informed when a contact preference should be deleted. */
159 public void setRemoveContactPreferenceListener(
160 RemoveContactPreferenceListener removeContactListener) {
161 mRemoveContactPreferenceListener = removeContactListener;
mariagpuyol53745bf2016-03-23 14:43:55 -0700162 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();
mariagpuyol609f68a2016-02-22 17:45:47 -0800186 }
187
mariagpuyold19ace52016-02-12 10:37:26 -0800188 @Override
Juan Lang2c0518a2017-05-10 17:00:32 -0700189 public void onBindViewHolder(PreferenceViewHolder holder) {
190 super.onBindViewHolder(holder);
191 View deleteContactIcon = holder.findViewById(R.id.delete_contact);
mariagpuyol609f68a2016-02-22 17:45:47 -0800192 if (mRemoveContactPreferenceListener == null) {
mariagpuyol16472ca2016-02-16 17:49:26 -0800193 deleteContactIcon.setVisibility(View.GONE);
194 } else {
mariagpuyol609f68a2016-02-22 17:45:47 -0800195 deleteContactIcon.setOnClickListener(new View.OnClickListener() {
196 @Override
197 public void onClick(View view) {
mariagpuyol53745bf2016-03-23 14:43:55 -0700198 showRemoveContactDialog(null);
mariagpuyol609f68a2016-02-22 17:45:47 -0800199 }
200 });
201
mariagpuyold19ace52016-02-12 10:37:26 -0800202 }
Akshay Kannancdb6c142016-01-20 18:25:44 -0800203 }
204
mariagpuyol08e2e512017-03-08 10:00:15 -0800205 public Uri getPhoneUri() {
206 return mContact.getPhoneUri();
mariagpuyol609f68a2016-02-22 17:45:47 -0800207 }
208
mariagpuyol9e2eea12016-04-01 18:10:12 -0700209 @VisibleForTesting
210 EmergencyContactManager.Contact getContact() {
211 return mContact;
212 }
213
214 @VisibleForTesting
215 AlertDialog getRemoveContactDialog() {
216 return mRemoveContactDialog;
217 }
218
Akshay Kannancdb6c142016-01-20 18:25:44 -0800219 /**
Akshay Kannan44ef39d2016-01-26 09:08:41 -0800220 * Calls the contact.
221 */
mariagpuyol5cf7f732016-03-08 09:48:15 -0800222 public void callContact() {
Tyler Gunn1c888f82018-04-15 22:49:59 -0700223 // 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);
mariagpuyol5cf7f732016-03-08 09:48:15 -0800227 MetricsLogger.action(getContext(), MetricsEvent.ACTION_CALL_EMERGENCY_CONTACT);
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}