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.content.Context;
     19 import android.content.SharedPreferences;
     20 import android.content.res.TypedArray;
     21 import android.net.Uri;
     22 import android.preference.Preference;
     23 import android.preference.PreferenceCategory;
     24 import android.preference.PreferenceManager;
     25 import android.util.AttributeSet;
     26 
     27 import com.android.emergency.EmergencyContactManager;
     28 import com.android.emergency.ReloadablePreferenceInterface;
     29 import com.android.internal.annotations.VisibleForTesting;
     30 import com.android.internal.logging.MetricsLogger;
     31 import com.android.internal.logging.MetricsProto.MetricsEvent;
     32 
     33 import java.util.ArrayList;
     34 import java.util.Collections;
     35 import java.util.Iterator;
     36 import java.util.List;
     37 import java.util.regex.Pattern;
     38 
     39 /**
     40  * Custom {@link PreferenceCategory} that deals with contacts being deleted from the contacts app.
     41  *
     42  * <p>Contacts are stored internally using their ContactsContract.CommonDataKinds.Phone.CONTENT_URI.
     43  */
     44 public class EmergencyContactsPreference extends PreferenceCategory
     45         implements ReloadablePreferenceInterface,
     46         ContactPreference.RemoveContactPreferenceListener {
     47 
     48     private static final String CONTACT_SEPARATOR = "|";
     49     private static final String QUOTE_CONTACT_SEPARATOR = Pattern.quote(CONTACT_SEPARATOR);
     50 
     51     /** Stores the emergency contact's ContactsContract.CommonDataKinds.Phone.CONTENT_URI */
     52     private List<Uri> mEmergencyContacts = new ArrayList<Uri>();
     53     private boolean mEmergencyContactsSet = false;
     54 
     55     public EmergencyContactsPreference(Context context, AttributeSet attrs) {
     56         super(context, attrs);
     57     }
     58 
     59     @Override
     60     protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
     61         setEmergencyContacts(restorePersistedValue ?
     62                 getPersistedEmergencyContacts() :
     63                 deserializeAndFilter(getKey(),
     64                         getContext(),
     65                         (String) defaultValue));
     66     }
     67 
     68     @Override
     69     protected Object onGetDefaultValue(TypedArray a, int index) {
     70         return a.getString(index);
     71     }
     72 
     73     @Override
     74     public void reloadFromPreference() {
     75         setEmergencyContacts(getPersistedEmergencyContacts());
     76     }
     77 
     78     @Override
     79     public boolean isNotSet() {
     80         return mEmergencyContacts.isEmpty();
     81     }
     82 
     83     @Override
     84     public void onRemoveContactPreference(ContactPreference contactPreference) {
     85         Uri newContact = contactPreference.getContactUri();
     86         if (mEmergencyContacts.contains(newContact)) {
     87             List<Uri> updatedContacts = new ArrayList<Uri>(mEmergencyContacts);
     88             if (updatedContacts.remove(newContact) && callChangeListener(updatedContacts)) {
     89                 MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETE_EMERGENCY_CONTACT);
     90                 setEmergencyContacts(updatedContacts);
     91             }
     92         }
     93     }
     94 
     95     /**
     96      * Adds a new emergency contact. The {@code contactUri} is the
     97      * ContactsContract.CommonDataKinds.Phone.CONTENT_URI corresponding to the
     98      * contact's selected phone number.
     99      */
    100     public void addNewEmergencyContact(Uri contactUri) {
    101         if (!mEmergencyContacts.contains(contactUri)) {
    102             List<Uri> updatedContacts = new ArrayList<Uri>(mEmergencyContacts);
    103             if (updatedContacts.add(contactUri) && callChangeListener(updatedContacts)) {
    104                 MetricsLogger.action(getContext(), MetricsEvent.ACTION_ADD_EMERGENCY_CONTACT);
    105                 setEmergencyContacts(updatedContacts);
    106             }
    107         }
    108     }
    109 
    110     @VisibleForTesting
    111     public List<Uri> getEmergencyContacts() {
    112         return mEmergencyContacts;
    113     }
    114 
    115     public void setEmergencyContacts(List<Uri> emergencyContacts) {
    116         final boolean changed = !mEmergencyContacts.equals(emergencyContacts);
    117         if (changed || !mEmergencyContactsSet) {
    118             mEmergencyContacts = emergencyContacts;
    119             mEmergencyContactsSet = true;
    120             persistString(serialize(emergencyContacts));
    121             if (changed) {
    122                 notifyChanged();
    123             }
    124         }
    125 
    126         while (getPreferenceCount() - emergencyContacts.size() > 0) {
    127             removePreference(getPreference(0));
    128         }
    129 
    130         // Reload the preferences or add new ones if necessary
    131         Iterator<Uri> it = emergencyContacts.iterator();
    132         int i = 0;
    133         while (it.hasNext()) {
    134             if (i < getPreferenceCount()) {
    135                 ContactPreference contactPreference = (ContactPreference) getPreference(i);
    136                 contactPreference.setUri(it.next());
    137             } else {
    138                 addContactPreference(it.next());
    139             }
    140             i++;
    141         }
    142     }
    143 
    144     private void addContactPreference(Uri contactUri) {
    145         final ContactPreference contactPreference = new ContactPreference(getContext(), contactUri);
    146         onBindContactView(contactPreference);
    147         addPreference(contactPreference);
    148     }
    149 
    150     /**
    151      * Called when {@code contactPreference} has been added to this category. You may now set
    152      * listeners.
    153      */
    154     protected void onBindContactView(final ContactPreference contactPreference) {
    155         contactPreference.setRemoveContactPreferenceListener(this);
    156         contactPreference
    157                 .setOnPreferenceClickListener(
    158                         new Preference.OnPreferenceClickListener() {
    159                             @Override
    160                             public boolean onPreferenceClick(Preference preference) {
    161                                 contactPreference.displayContact();
    162                                 return true;
    163                             }
    164                         }
    165                 );
    166     }
    167 
    168     private List<Uri> getPersistedEmergencyContacts() {
    169         return deserializeAndFilter(getKey(), getContext(), getPersistedString(""));
    170     }
    171 
    172     @Override
    173     protected String getPersistedString(String defaultReturnValue) {
    174         try {
    175             return super.getPersistedString(defaultReturnValue);
    176         } catch (ClassCastException e) {
    177             // Protect against b/28194605: We used to store the contacts using a string set.
    178             // If it is a string set, ignore its value. If it is not a string set it will throw
    179             // a ClassCastException
    180             getPersistedStringSet(Collections.<String>emptySet());
    181             return defaultReturnValue;
    182         }
    183     }
    184 
    185     /**
    186      * Converts the string representing the emergency contacts to a list of Uris and only keeps
    187      * those corresponding to still existing contacts. It persists the contacts if at least one
    188      * contact was does not exist anymore.
    189      */
    190     public static List<Uri> deserializeAndFilter(String key, Context context,
    191                                                  String emergencyContactString) {
    192         String[] emergencyContactsArray =
    193                 emergencyContactString.split(QUOTE_CONTACT_SEPARATOR);
    194         List<Uri> filteredEmergencyContacts = new ArrayList<Uri>(emergencyContactsArray.length);
    195         for (String emergencyContact : emergencyContactsArray) {
    196             Uri contactUri = Uri.parse(emergencyContact);
    197             if (EmergencyContactManager.isValidEmergencyContact(context, contactUri)) {
    198                 filteredEmergencyContacts.add(contactUri);
    199             }
    200         }
    201         // If not all contacts were added, then we need to overwrite the emergency contacts stored
    202         // in shared preferences. This deals with emergency contacts being deleted from contacts:
    203         // currently we have no way to being notified when this happens.
    204         if (filteredEmergencyContacts.size() != emergencyContactsArray.length) {
    205             String emergencyContactStrings = serialize(filteredEmergencyContacts);
    206             SharedPreferences sharedPreferences =
    207                     PreferenceManager.getDefaultSharedPreferences(context);
    208             sharedPreferences.edit().putString(key, emergencyContactStrings).commit();
    209         }
    210         return filteredEmergencyContacts;
    211     }
    212 
    213     /** Converts the Uris to a string representation. */
    214     public static String serialize(List<Uri> emergencyContacts) {
    215         StringBuilder sb = new StringBuilder();
    216         for (int i = 0; i < emergencyContacts.size(); i++) {
    217             sb.append(emergencyContacts.get(i).toString());
    218             sb.append(CONTACT_SEPARATOR);
    219         }
    220 
    221         if (sb.length() > 0) {
    222             sb.setLength(sb.length() - 1);
    223         }
    224         return sb.toString();
    225     }
    226 }
    227