Home | History | Annotate | Download | only in preferences
      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