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