Add new class EmergencyContactManager

This refactor moves functionality related to reading, adding or removing
contacts to the list of emergency contacts to EmergencyContactManager.
Change-Id: I186fd48b30abd0f817ab73e4b19373761cb22508
diff --git a/Android.mk b/Android.mk
index 15ae845..e5aa05f 100644
--- a/Android.mk
+++ b/Android.mk
@@ -24,27 +24,27 @@
 LOCAL_MODULE_TAGS := eng
 LOCAL_JAVA_LIBRARIES := framework
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-v14-preference \
-                               android-support-v13 \
-                               android-support-v7-appcompat \
-			       android-support-v7-preference \
-			       android-support-v7-recyclerview \
-			       android-support-v4 \
-			       android-support-design
+    android-support-v13 \
+    android-support-v7-appcompat \
+	android-support-v7-preference \
+	android-support-v7-recyclerview \
+	android-support-v4 \
+	android-support-design
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_RESOURCE_DIR += $(LOCAL_PATH)/res \
-		      $(support_library_root_dir)/v7/appcompat/res \
-		      $(support_library_root_dir)/v7/preference/res \
-		      $(support_library_root_dir)/v7/recyclerview/res \
-		      $(support_library_root_dir)/v14/preference/res \
-                      $(support_library_root_dir)/design/res
+	$(support_library_root_dir)/v7/appcompat/res \
+	$(support_library_root_dir)/v7/preference/res \
+	$(support_library_root_dir)/v7/recyclerview/res \
+	$(support_library_root_dir)/v14/preference/res \
+    $(support_library_root_dir)/design/res
 
 LOCAL_AAPT_FLAGS := --auto-add-overlay \
-                    --extra-packages android.support.v7.preference \
-		    --extra-packages android.support.v14.preference \
-		    --extra-packages android.support.v7.appcompat \
-		    --extra-packages android.support.v7.recyclerview \
-		    --extra-packages android.support.design
+    --extra-packages android.support.v7.preference \
+	--extra-packages android.support.v14.preference \
+	--extra-packages android.support.v7.appcompat \
+	--extra-packages android.support.v7.recyclerview \
+	--extra-packages android.support.design
 
 LOCAL_SDK_VERSION := current
 LOCAL_PACKAGE_NAME := EmergencyInfo
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 03ae85c..8ad3149 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -57,7 +57,6 @@
     <string name="remove_contact">Remove %s from emergency contacts?</string>
     <string name="remove">Remove</string>
     <string name="cancel">Cancel</string>
-    <string name="unknown_contact">Unknown contact</string>
 
     <string name="description">This information can help first responders in an emergency. It is
         stored on your device only, but anyone can read it from the emergency dialer without 
diff --git a/src/com/android/emergency/ContactPreference.java b/src/com/android/emergency/ContactPreference.java
index 8fd15ba..b7d5e4b 100644
--- a/src/com/android/emergency/ContactPreference.java
+++ b/src/com/android/emergency/ContactPreference.java
@@ -17,18 +17,15 @@
 
 import android.Manifest;
 import android.app.AlertDialog;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.database.Cursor;
 import android.net.Uri;
 import android.preference.Preference;
-import android.provider.ContactsContract;
+import android.support.annotation.NonNull;
 import android.support.v4.app.ActivityCompat;
 import android.view.View;
-import android.widget.Toast;
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.MetricsProto.MetricsEvent;
@@ -41,6 +38,7 @@
 
     private final Uri mUri;
     private final DeleteContactListener mDeleteContactListener;
+    private final String mName;
 
     /**
      * Listener for deleting a contact.
@@ -49,20 +47,23 @@
         /**
          * Callback to delete a contact.
          */
-        void onContactDelete(String contactUri);
+        void onContactDelete(Uri contactUri);
     }
 
     /**
      * Instantiates a ContactPreference that displays an emergency contact, taking in a Context and
-     * the Uri of the contact as a String.
+     * the Uri, name and phone number of the contact and a listener to be informed when clicking on
+     * the delete icon.
      */
-    public ContactPreference(Context context, String uriString,
-                             DeleteContactListener deleteContactListener) {
+    public ContactPreference(Context context,
+                             @NonNull Uri contactUri,
+                             @NonNull String contactName,
+                             @NonNull DeleteContactListener deleteContactListener) {
         super(context);
-        mUri = Uri.parse(uriString);
+        mUri = contactUri;
+        mName = contactName;
         mDeleteContactListener = deleteContactListener;
-        String name = getName();
-        setTitle((name != null) ? name : getContext().getString(R.string.unknown_contact));
+        setTitle(mName);
         setWidgetLayoutResource(R.layout.preference_user_delete_widget);
     }
 
@@ -76,12 +77,12 @@
                 public void onClick(View view) {
                     AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
                     builder.setMessage(String.format(getContext()
-                            .getString(R.string.remove_contact), getName()));
+                            .getString(R.string.remove_contact), mName));
                     builder.setPositiveButton(getContext().getString(R.string.remove),
                             new DialogInterface.OnClickListener() {
                                 @Override
                                 public void onClick(DialogInterface dialogInterface, int which) {
-                                    mDeleteContactListener.onContactDelete(mUri.toString());
+                                    mDeleteContactListener.onContactDelete(mUri);
                                 }
                             }).setNegativeButton(getContext().getString(R.string.cancel), null);
                     builder.create().show();
@@ -94,19 +95,16 @@
      * Calls the contact.
      */
     public void callContact() {
-        Uri number = getNumber();
-        if (number == null) {
-            String errorMessage = getContext().getString(R.string.phone_number_error);
-            Toast errorToast = Toast.makeText(getContext(), errorMessage, Toast.LENGTH_SHORT);
-            // TODO: Get toast to display over lock screen
-            errorToast.show();
-        } else {
-            Intent callIntent = new Intent(Intent.ACTION_CALL, number);
+        String phoneNumber = EmergencyContactManager.getNumber(getContext(), mUri);
+        if (phoneNumber != null) {
             if (ActivityCompat.checkSelfPermission(getContext(),
                     Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
+                Intent callIntent = new Intent(Intent.ACTION_CALL, Uri.parse("tel:" + phoneNumber));
                 MetricsLogger.action(getContext(), MetricsEvent.ACTION_CALL_EMERGENCY_CONTACT);
                 getContext().startActivity(callIntent);
             }
+        } else {
+            // TODO: Show dialog saying that there is no number.
         }
     }
 
@@ -118,55 +116,4 @@
         contactIntent.setData(mUri);
         getContext().startActivity(contactIntent);
     }
-
-    /**
-     * Returns the URI for the contact.
-     */
-    public Uri getUri() {
-        return mUri;
-    }
-
-    private String getName() {
-        Cursor cursor = getContext().getContentResolver().query(getUri(), null, null, null, null);
-        try {
-            if (cursor != null && cursor.moveToFirst()) {
-                return cursor.getString(cursor.getColumnIndex(
-                        ContactsContract.Contacts.DISPLAY_NAME));
-            }
-        } finally {
-            cursor.close();
-        }
-        return null;
-    }
-
-    private Uri getNumber() {
-        // TODO: Investigate if this can be done in 1 query instead of 2.
-        ContentResolver contentResolver = getContext().getContentResolver();
-        Cursor contactCursor = contentResolver.query(getUri(), null, null, null, null);
-        try {
-            if (contactCursor != null && contactCursor.moveToFirst()) {
-                String id = contactCursor.getString(
-                        contactCursor.getColumnIndex(ContactsContract.Contacts._ID));
-                if (contactCursor.getInt(contactCursor.getColumnIndex(
-                        ContactsContract.Contacts.HAS_PHONE_NUMBER)) != 0) {
-                    Cursor phoneCursor = contentResolver.query(
-                            ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
-                            ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
-                            new String[]{id}, null);
-                    try {
-                        if (phoneCursor != null && phoneCursor.moveToFirst()) {
-                            return Uri.parse("tel:" + phoneCursor.getString(
-                                    phoneCursor.getColumnIndex(
-                                            ContactsContract.CommonDataKinds.Phone.NUMBER)));
-                        }
-                    } finally {
-                        phoneCursor.close();
-                    }
-                }
-            }
-        } finally {
-            contactCursor.close();
-        }
-        return null;
-    }
 }
diff --git a/src/com/android/emergency/EmergencyContactManager.java b/src/com/android/emergency/EmergencyContactManager.java
new file mode 100644
index 0000000..a295dc2
--- /dev/null
+++ b/src/com/android/emergency/EmergencyContactManager.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.emergency;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.net.Uri;
+import android.provider.ContactsContract;
+import android.util.ArraySet;
+
+import java.util.Collections;
+import java.util.Set;
+
+/**
+ * Manages emergency contacts of the user.
+ */
+public class EmergencyContactManager {
+    private final SharedPreferences mSharedPreferences;
+    private final String mKey;
+    private final Context mContext;
+
+    /**
+     * Creates a new instance initialized with context and the shared preferences used to store the
+     * emergency contacts under the specified key.
+     */
+    public EmergencyContactManager(Context context, SharedPreferences sharedPreferences,
+                                   String key) {
+        mContext = context;
+        mSharedPreferences = sharedPreferences;
+        mKey = key;
+    }
+
+    /**
+     * Adds a new contact to the emergency contacts. */
+    public void addContact(Uri contactUri) {
+        // TODO: Consider refactoring this to use always setContacts() rather than
+        // addContact()/removeContact()
+        Set<Uri> emergencyContacts = getEmergencyContacts();
+        if (emergencyContacts.add(contactUri)) {
+            setEmergencyContacts(emergencyContacts);
+        }
+    }
+
+    /** Removes the specified contact from the list of emergency contacts. */
+    public void removeContact(Uri contactUri) {
+        // TODO: Consider refactoring this to use always setContacts() rather than
+        // addContact()/removeContact()
+        Set<Uri> emergencyContacts = getEmergencyContacts();
+        if (emergencyContacts.remove(contactUri)) {
+            setEmergencyContacts(emergencyContacts);
+        }
+    }
+
+    public Set<Uri> getEmergencyContacts() {
+        Set<String> emergencyContactStrings = mSharedPreferences.getStringSet(mKey,
+                Collections.<String>emptySet());
+        Set<Uri> emergencyContacts = new ArraySet<Uri>(emergencyContactStrings.size());
+        for (String emergencyContact : emergencyContactStrings) {
+            Uri contactUri = Uri.parse(emergencyContact);
+            if (isValidEmergencyContact(contactUri)) {
+                emergencyContacts.add(contactUri);
+            }
+        }
+        // If not all contacts were added, then we need to overwrite the emergency contacts stored
+        // in shared preferences. This deals with emergency contacts being deleted from contacts:
+        // currently we have no way to being notified when this happens.
+        if (emergencyContacts.size() != emergencyContactStrings.size()) {
+            setEmergencyContacts(emergencyContacts);
+        }
+        return emergencyContacts;
+    }
+
+    /** Returns the display name of the contact. */
+    public static String getName(Context context, Uri contactUri) {
+        Cursor cursor = context.getContentResolver().query(contactUri, null, null, null, null);
+        try {
+            if (cursor != null && cursor.moveToFirst()) {
+                return cursor.getString(cursor.getColumnIndex(
+                        ContactsContract.Contacts.DISPLAY_NAME));
+            }
+        } finally {
+            cursor.close();
+        }
+        return null;
+    }
+
+    private static boolean contactExists(Context context, Uri contactUri) {
+        Cursor cursor = context.getContentResolver().query(contactUri, null, null, null, null);
+        try {
+            if (cursor != null && cursor.moveToFirst()) {
+                return true;
+            }
+        } finally {
+            cursor.close();
+        }
+        return false;
+    }
+
+    /** Returns the phone number of the contact. */
+    public static String getNumber(Context context, Uri contactUri) {
+        // TODO: Investigate if this can be done in 1 query instead of 2.
+        ContentResolver contentResolver = context.getContentResolver();
+        Cursor contactCursor = contentResolver.query(contactUri, null, null, null, null);
+        try {
+            if (contactCursor != null && contactCursor.moveToFirst()) {
+                String id = contactCursor.getString(
+                        contactCursor.getColumnIndex(ContactsContract.Contacts._ID));
+                if (contactCursor.getInt(contactCursor.getColumnIndex(
+                        ContactsContract.Contacts.HAS_PHONE_NUMBER)) != 0) {
+                    Cursor phoneCursor = contentResolver.query(
+                            ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
+                            ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?",
+                            new String[]{id}, null);
+                    try {
+                        if (phoneCursor != null && phoneCursor.moveToFirst()) {
+                            return phoneCursor.getString(
+                                    phoneCursor.getColumnIndex(
+                                            ContactsContract.CommonDataKinds.Phone.NUMBER));
+                        }
+                    } finally {
+                        phoneCursor.close();
+                    }
+                }
+            }
+        } finally {
+            contactCursor.close();
+        }
+        return null;
+    }
+
+    /** Returns whether the contact uri is not null and corresponds to an existing contact. */
+    private boolean isValidEmergencyContact(Uri contactUri) {
+        return contactUri != null && contactExists(mContext, contactUri);
+    }
+
+    private void setEmergencyContacts(Set<Uri> emergencyContacts) {
+        Set<String> emergencyContactStrings = new ArraySet<String>(emergencyContacts.size());
+        for (Uri contactUri : emergencyContacts) {
+            emergencyContactStrings.add(contactUri.toString());
+        }
+        mSharedPreferences.edit().putStringSet(mKey, emergencyContactStrings).commit();
+    }
+}
diff --git a/src/com/android/emergency/EmergencyInfoFragment.java b/src/com/android/emergency/EmergencyInfoFragment.java
index 08dec6b..bb60664 100644
--- a/src/com/android/emergency/EmergencyInfoFragment.java
+++ b/src/com/android/emergency/EmergencyInfoFragment.java
@@ -31,7 +31,6 @@
 import android.provider.ContactsContract;
 import android.support.design.widget.FloatingActionButton;
 import android.support.v4.content.ContextCompat;
-import android.util.ArraySet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -39,7 +38,6 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.MetricsProto.MetricsEvent;
 
-import java.util.Collections;
 import java.util.Set;
 
 /**
@@ -77,6 +75,9 @@
     /** SharedPreferences- initialized in onCreate */
     private SharedPreferences mSharedPreferences = null;
 
+    /** Emergency contact manager that handles adding an removing emergency contacts. */
+    private EmergencyContactManager mEmergencyContactManager;
+
     /** Reference to the preferenceScreen controlled by this fragment */
     private PreferenceScreen mPreferenceScreen;
 
@@ -121,6 +122,8 @@
 
         mReadOnly = getArguments().getBoolean(READ_ONLY_KEY);
         mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(getContext());
+        mEmergencyContactManager = new EmergencyContactManager(getContext(), mSharedPreferences,
+                EMERGENCY_CONTACTS_KEY);
         mPreferenceScreen = getPreferenceScreen();
 
         for (String preferenceKey : PREFERENCE_KEYS) {
@@ -144,11 +147,14 @@
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (requestCode == CONTACT_PICKER_RESULT && resultCode == Activity.RESULT_OK) {
-            // TODO: If there are no phone numbers, prevent the user from adding the contact.
-            // TODO: If there are multiple phone numbers, ask the user to pick one.
             Uri uri = data.getData();
-            addContact(uri.toString());
+            mEmergencyContactManager.addContact(uri);
+            MetricsLogger.action(getContext(), MetricsEvent.ACTION_ADD_EMERGENCY_CONTACT);
             populateEmergencyContacts();
+            // TODO: If there are multiple phone numbers, ask the user to pick one.
+            if (EmergencyContactManager.getNumber(getContext(), uri) == null) {
+                // TODO: show warning dialog: no phone number
+            }
         }
     }
 
@@ -165,8 +171,9 @@
     }
 
     @Override
-    public void onContactDelete(String contactUri) {
-        deleteContact(contactUri);
+    public void onContactDelete(Uri contactUri) {
+        mEmergencyContactManager.removeContact(contactUri);
+        MetricsLogger.action(getContext(), MetricsEvent.ACTION_DELETE_EMERGENCY_CONTACT);
         populateEmergencyContacts();
     }
 
@@ -214,8 +221,9 @@
     private void populateEmergencyContacts() {
         PreferenceCategory emergencyContactsCategory =
                 (PreferenceCategory) findPreference(EMERGENCY_CONTACTS_KEY);
+        // TODO: Use a list adapter instead of removing all each time.
         emergencyContactsCategory.removeAll();
-        Set<String> emergencyContacts = getEmergencyContacts();
+        Set<Uri> emergencyContacts = mEmergencyContactManager.getEmergencyContacts();
 
         if (!emergencyContacts.isEmpty()) {
             // Get permission if necessary, else populate emergency contacts list
@@ -225,12 +233,15 @@
                     Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED;
             if (!hasContactsPermission || !hasCallPermission) {
                 requestPermissions(new String[]{
-                                Manifest.permission.READ_CONTACTS,
-                                Manifest.permission.CALL_PHONE}, PERMISSION_REQUEST);
+                        Manifest.permission.READ_CONTACTS,
+                        Manifest.permission.CALL_PHONE}, PERMISSION_REQUEST);
             } else {
-                for (String contactUri : emergencyContacts) {
+                for (Uri contactUri : emergencyContacts) {
                     final ContactPreference contactPreference =
-                            new ContactPreference(getContext(), contactUri, this);
+                            new ContactPreference(getContext(),
+                                    contactUri,
+                                    EmergencyContactManager.getName(getContext(), contactUri),
+                                    this);
                     contactPreference.setOnPreferenceClickListener(
                             createContactPreferenceClickListener(contactPreference));
                     emergencyContactsCategory.addPreference(contactPreference);
@@ -241,39 +252,12 @@
         if (!mReadOnly) {
             // If in edit mode, add a button to create a new emergency contact.
             emergencyContactsCategory.addPreference(createAddEmergencyContactPreference());
-        } else if (emergencyContacts.isEmpty()) {
+        } else if (emergencyContactsCategory.getPreferenceCount() == 0) {
             // If in view mode and there are no contacts, remove the section entirely.
             mPreferenceScreen.removePreference(emergencyContactsCategory);
         }
     }
 
-    private void addContact(String contactUri) {
-        Set<String> oldContacts = getEmergencyContacts();
-        if (!oldContacts.contains(contactUri)) {
-            // Manipulate a copy of emergency contacts rather than editing directly- see
-            // getEmergencyContacts for why this is necessary.
-            ArraySet<String> newContacts = new ArraySet<String>(oldContacts.size() + 1);
-            newContacts.addAll(oldContacts);
-            newContacts.add(contactUri);
-            setEmergencyContacts(newContacts);
-            MetricsLogger.action(getContext(), MetricsEvent.ACTION_ADD_EMERGENCY_CONTACT);
-        }
-    }
-
-    private void deleteContact(String contactUri) {
-        Set<String> oldContacts = getEmergencyContacts();
-        if (oldContacts.contains(contactUri)) {
-            // Manipulate a copy of emergency contacts rather than editing directly- see
-            // getEmergencyContacts for why this is necessary.
-            ArraySet<String> newContacts = new ArraySet<String>(oldContacts.size());
-            newContacts.addAll(oldContacts);
-            newContacts.remove(contactUri);
-            setEmergencyContacts(newContacts);
-            MetricsLogger.action(getContext(),
-                    MetricsEvent.ACTION_DELETE_EMERGENCY_CONTACT);
-        }
-    }
-
     private Preference.OnPreferenceClickListener createContactPreferenceClickListener(
             final ContactPreference contactPreference) {
         return new Preference.OnPreferenceClickListener() {
@@ -307,21 +291,4 @@
         });
         return addEmergencyContact;
     }
-
-
-    /**
-     * Returns a Set of stored emergency contacts. If editing, make a copy of the set as
-     * described by {@link SharedPreferences#getStringSet(String, Set<String>)}, then call
-     * {@link #setEmergencyContacts(Set)} to store the new contact information.
-     */
-    private Set<String> getEmergencyContacts() {
-        Set<String> emergencyContacts = mSharedPreferences
-                .getStringSet(EMERGENCY_CONTACTS_KEY, Collections.<String>emptySet());
-        return emergencyContacts;
-    }
-
-    private void setEmergencyContacts(Set<String> emergencyContacts) {
-        mSharedPreferences.edit().putStringSet(EMERGENCY_CONTACTS_KEY, emergencyContacts)
-                .commit();
-    }
 }