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 EmergencyContactsPreference(Context context, AttributeSet attrs)55 public EmergencyContactsPreference(Context context, AttributeSet attrs) { 56 super(context, attrs); 57 } 58 59 @Override onSetInitialValue(boolean restorePersistedValue, Object defaultValue)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 onGetDefaultValue(TypedArray a, int index)69 protected Object onGetDefaultValue(TypedArray a, int index) { 70 return a.getString(index); 71 } 72 73 @Override reloadFromPreference()74 public void reloadFromPreference() { 75 setEmergencyContacts(getPersistedEmergencyContacts()); 76 } 77 78 @Override isNotSet()79 public boolean isNotSet() { 80 return mEmergencyContacts.isEmpty(); 81 } 82 83 @Override onRemoveContactPreference(ContactPreference contactPreference)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 */ addNewEmergencyContact(Uri contactUri)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 getEmergencyContacts()111 public List<Uri> getEmergencyContacts() { 112 return mEmergencyContacts; 113 } 114 setEmergencyContacts(List<Uri> emergencyContacts)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 addContactPreference(Uri contactUri)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 */ onBindContactView(final ContactPreference contactPreference)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 getPersistedEmergencyContacts()168 private List<Uri> getPersistedEmergencyContacts() { 169 return deserializeAndFilter(getKey(), getContext(), getPersistedString("")); 170 } 171 172 @Override getPersistedString(String defaultReturnValue)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 */ deserializeAndFilter(String key, Context context, String emergencyContactString)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. */ serialize(List<Uri> emergencyContacts)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