Add robolectric tests for EmergencyContactsPreference.

Remove instrumentation tests they replace.

Bug: 30046624
Test: make RunEmergencyInfoRoboTests
Change-Id: I3ef549d55b573bd2132930ac1aea22c1627433c0
(cherry picked from commit ec15f8b319f9e38c064f8b521dba6c2dfa0a4117)
diff --git a/src/com/android/emergency/preferences/ContactPreference.java b/src/com/android/emergency/preferences/ContactPreference.java
index ebe1dea..7194b5e 100644
--- a/src/com/android/emergency/preferences/ContactPreference.java
+++ b/src/com/android/emergency/preferences/ContactPreference.java
@@ -54,6 +54,13 @@
 
     private static final String TAG = "ContactPreference";
 
+    static final ContactFactory DEFAULT_CONTACT_FACTORY = new ContactFactory() {
+        @Override
+        public EmergencyContactManager.Contact getContact(Context context, Uri phoneUri) {
+            return EmergencyContactManager.getContact(context, phoneUri);
+        }
+    };
+
     private final ContactFactory mContactFactory;
     private EmergencyContactManager.Contact mContact;
     @Nullable private RemoveContactPreferenceListener mRemoveContactPreferenceListener;
@@ -88,16 +95,11 @@
      * the Uri.
      */
     public ContactPreference(Context context, @NonNull Uri phoneUri) {
-        this(context, phoneUri, new ContactFactory() {
-            @Override
-            public EmergencyContactManager.Contact getContact(Context context, Uri phoneUri) {
-                return EmergencyContactManager.getContact(context, phoneUri);
-            }
-        });
+        this(context, phoneUri, DEFAULT_CONTACT_FACTORY);
     }
 
     @VisibleForTesting
-    public ContactPreference(Context context, @NonNull Uri phoneUri,
+    ContactPreference(Context context, @NonNull Uri phoneUri,
             @NonNull ContactFactory contactFactory) {
         super(context);
         mContactFactory = contactFactory;
diff --git a/src/com/android/emergency/preferences/EmergencyContactsPreference.java b/src/com/android/emergency/preferences/EmergencyContactsPreference.java
index 166e385..ff9d0ca 100644
--- a/src/com/android/emergency/preferences/EmergencyContactsPreference.java
+++ b/src/com/android/emergency/preferences/EmergencyContactsPreference.java
@@ -22,6 +22,7 @@
 import android.preference.Preference;
 import android.preference.PreferenceCategory;
 import android.preference.PreferenceManager;
+import android.support.annotation.NonNull;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.widget.Toast;
@@ -52,13 +53,44 @@
 
     private static final String CONTACT_SEPARATOR = "|";
     private static final String QUOTE_CONTACT_SEPARATOR = Pattern.quote(CONTACT_SEPARATOR);
+    private static final ContactValidator DEFAULT_CONTACT_VALIDATOR = new ContactValidator() {
+        @Override
+        public boolean isValidEmergencyContact(Context context, Uri phoneUri) {
+            return EmergencyContactManager.isValidEmergencyContact(context, phoneUri);
+        }
+    };
 
+    private final ContactValidator mContactValidator;
+    private final ContactPreference.ContactFactory mContactFactory;
     /** Stores the emergency contact's ContactsContract.CommonDataKinds.Phone.CONTENT_URI */
     private List<Uri> mEmergencyContacts = new ArrayList<Uri>();
     private boolean mEmergencyContactsSet = false;
 
+    /**
+     * Interface for getting a contact for a phone number Uri.
+     */
+    public interface ContactValidator {
+        /**
+         * Checks whether a given phone Uri represents a valid emergency contact.
+         *
+         * @param context The context to use.
+         * @param phoneUri The phone uri.
+         * @return whether the given phone Uri is a valid emergency contact.
+         */
+        boolean isValidEmergencyContact(Context context, Uri phoneUri);
+    }
+
     public EmergencyContactsPreference(Context context, AttributeSet attrs) {
+        this(context, attrs, DEFAULT_CONTACT_VALIDATOR, ContactPreference.DEFAULT_CONTACT_FACTORY);
+    }
+
+    @VisibleForTesting
+    EmergencyContactsPreference(Context context, AttributeSet attrs,
+            @NonNull ContactValidator contactValidator,
+            @NonNull ContactPreference.ContactFactory contactFactory) {
         super(context, attrs);
+        mContactValidator = contactValidator;
+        mContactFactory = contactFactory;
     }
 
     @Override
@@ -67,7 +99,8 @@
                 getPersistedEmergencyContacts() :
                 deserializeAndFilter(getKey(),
                         getContext(),
-                        (String) defaultValue));
+                        (String) defaultValue,
+                        mContactValidator));
     }
 
     @Override
@@ -106,7 +139,7 @@
         if (mEmergencyContacts.contains(phoneUri)) {
             return;
         }
-        if (!EmergencyContactManager.isValidEmergencyContact(getContext(), phoneUri)) {
+        if (!mContactValidator.isValidEmergencyContact(getContext(), phoneUri)) {
             Toast.makeText(getContext(), getContext().getString(R.string.fail_add_contact),
                 Toast.LENGTH_LONG).show();
             return;
@@ -128,7 +161,7 @@
         if (changed || !mEmergencyContactsSet) {
             mEmergencyContacts = emergencyContacts;
             mEmergencyContactsSet = true;
-            persistString(serialize(emergencyContacts));
+            persistEmergencyContacts(emergencyContacts);
             if (changed) {
                 notifyChanged();
             }
@@ -153,7 +186,8 @@
                     contactPreference = (ContactPreference) getPreference(i);
                     contactPreference.setPhoneUri(phoneUri);
                 } else {
-                    contactPreference = new ContactPreference(getContext(), phoneUri);
+                    contactPreference =
+                            new ContactPreference(getContext(), phoneUri, mContactFactory);
                     onBindContactView(contactPreference);
                     addPreference(contactPreference);
                 }
@@ -198,7 +232,8 @@
     }
 
     private List<Uri> getPersistedEmergencyContacts() {
-        return deserializeAndFilter(getKey(), getContext(), getPersistedString(""));
+        return deserializeAndFilter(getKey(), getContext(), getPersistedString(""),
+                mContactValidator);
     }
 
     @Override
@@ -221,25 +256,8 @@
      */
     public static List<Uri> deserializeAndFilter(String key, Context context,
                                                  String emergencyContactString) {
-        String[] emergencyContactsArray =
-                emergencyContactString.split(QUOTE_CONTACT_SEPARATOR);
-        List<Uri> filteredEmergencyContacts = new ArrayList<Uri>(emergencyContactsArray.length);
-        for (String emergencyContact : emergencyContactsArray) {
-            Uri phoneUri = Uri.parse(emergencyContact);
-            if (EmergencyContactManager.isValidEmergencyContact(context, phoneUri)) {
-                filteredEmergencyContacts.add(phoneUri);
-            }
-        }
-        // 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 (filteredEmergencyContacts.size() != emergencyContactsArray.length) {
-            String emergencyContactStrings = serialize(filteredEmergencyContacts);
-            SharedPreferences sharedPreferences =
-                    PreferenceManager.getDefaultSharedPreferences(context);
-            sharedPreferences.edit().putString(key, emergencyContactStrings).commit();
-        }
-        return filteredEmergencyContacts;
+        return deserializeAndFilter(key, context, emergencyContactString,
+                DEFAULT_CONTACT_VALIDATOR);
     }
 
     /** Converts the Uris to a string representation. */
@@ -255,4 +273,33 @@
         }
         return sb.toString();
     }
+
+    @VisibleForTesting
+    void persistEmergencyContacts(List<Uri> emergencyContacts) {
+        persistString(serialize(emergencyContacts));
+    }
+
+    private static List<Uri> deserializeAndFilter(String key, Context context,
+                                                  String emergencyContactString,
+                                                  ContactValidator contactValidator) {
+        String[] emergencyContactsArray =
+                emergencyContactString.split(QUOTE_CONTACT_SEPARATOR);
+        List<Uri> filteredEmergencyContacts = new ArrayList<Uri>(emergencyContactsArray.length);
+        for (String emergencyContact : emergencyContactsArray) {
+            Uri phoneUri = Uri.parse(emergencyContact);
+            if (contactValidator.isValidEmergencyContact(context, phoneUri)) {
+                filteredEmergencyContacts.add(phoneUri);
+            }
+        }
+        // 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 (filteredEmergencyContacts.size() != emergencyContactsArray.length) {
+            String emergencyContactStrings = serialize(filteredEmergencyContacts);
+            SharedPreferences sharedPreferences =
+                    PreferenceManager.getDefaultSharedPreferences(context);
+            sharedPreferences.edit().putString(key, emergencyContactStrings).commit();
+        }
+        return filteredEmergencyContacts;
+    }
 }
diff --git a/tests/instrumentation/src/com/android/emergency/preferences/EmergencyContactsPreferenceTest.java b/tests/instrumentation/src/com/android/emergency/preferences/EmergencyContactsPreferenceTest.java
index 45bea8b..ac4b1e0 100644
--- a/tests/instrumentation/src/com/android/emergency/preferences/EmergencyContactsPreferenceTest.java
+++ b/tests/instrumentation/src/com/android/emergency/preferences/EmergencyContactsPreferenceTest.java
@@ -77,101 +77,6 @@
         super.tearDown();
     }
 
-    public void testEmptyState() {
-        assertThat(mEmergencyContactsPreference).isNotNull();
-        assertThat(mEmergencyContactsPreference.isPersistent()).isTrue();
-        assertThat(mEmergencyContactsPreference.isNotSet()).isTrue();
-        assertThat(mEmergencyContactsPreference.getEmergencyContacts()).isEmpty();
-        assertThat(mEmergencyContactsPreference.getPreferenceCount()).isEqualTo(0);
-    }
-
-    public void testAddAndRemoveEmergencyContact() throws Throwable {
-        final String name = "Jane";
-        final String phoneNumber = "456";
-
-        final Uri phoneUri =
-                ContactTestUtils.createContact(mContentResolver, name, phoneNumber);
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mEmergencyContactsPreference.addNewEmergencyContact(phoneUri);
-            }
-        });
-
-        assertThat(mEmergencyContactsPreference.getEmergencyContacts().size()).isEqualTo(1);
-        assertThat(mEmergencyContactsPreference.getPreferenceCount()).isEqualTo(1);
-        ContactPreference contactPreference = (ContactPreference)
-                mEmergencyContactsPreference.getPreference(0);
-
-        assertThat(contactPreference.getPhoneUri()).isEqualTo(phoneUri);
-        assertThat(contactPreference.getTitle()).isEqualTo(name);
-        assertThat((String) contactPreference.getSummary()).contains(phoneNumber);
-
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mEmergencyContactsPreference.onRemoveContactPreference(
-                        (ContactPreference) mEmergencyContactsPreference.getPreference(0));
-            }
-        });
-
-        assertThat(mEmergencyContactsPreference.getEmergencyContacts()).isEmpty();
-        assertThat(mEmergencyContactsPreference.getPreferenceCount()).isEqualTo(0);
-
-        // Clean up the inserted contact
-        assertThat(ContactTestUtils.deleteContact(mContentResolver, name, phoneNumber)).isTrue();
-    }
-
-    public void testReloadFromPreference() throws Throwable {
-        final String nameJane = "Jane";
-        final String phoneNumberJane = "456";
-        final Uri emergencyContactJane = ContactTestUtils
-                .createContact(mContentResolver, nameJane, phoneNumberJane);
-
-        final String nameJohn = "John";
-        final String phoneNumberJohn = "123";
-        final Uri emergencyContactJohn = ContactTestUtils
-                .createContact(mContentResolver, nameJohn, phoneNumberJohn);
-
-        final List<Uri> emergencyContacts = new ArrayList<>();
-        emergencyContacts.add(emergencyContactJane);
-        emergencyContacts.add(emergencyContactJohn);
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mEmergencyContactsPreference.setEmergencyContacts(emergencyContacts);
-            }
-        });
-
-        assertThat(mEmergencyContactsPreference.getEmergencyContacts().size()).isEqualTo(2);
-        assertThat(mEmergencyContactsPreference.getPreferenceCount()).isEqualTo(2);
-
-        // Delete Jane from another app (e.g. contacts)
-        assertThat(ContactTestUtils
-                .deleteContact(mContentResolver, nameJane, phoneNumberJane)).isTrue();
-        getInstrumentation().waitForIdleSync();
-
-        runTestOnUiThread(new Runnable() {
-            @Override
-            public void run() {
-                mEmergencyContactsPreference.reloadFromPreference();
-            }
-        });
-
-        getInstrumentation().waitForIdleSync();
-
-        // Assert the only remaining contact is John
-        assertThat(mEmergencyContactsPreference.getEmergencyContacts().size()).isEqualTo(1);
-        assertThat(mEmergencyContactsPreference.getPreferenceCount()).isEqualTo(1);
-        ContactPreference contactPreference = (ContactPreference)
-                mEmergencyContactsPreference.getPreference(0);
-        assertThat(contactPreference.getPhoneUri()).isEqualTo(emergencyContactJohn);
-
-        // Clean up the inserted contact
-        assertThat(ContactTestUtils
-                .deleteContact(mContentResolver, nameJohn, phoneNumberJohn)).isTrue();
-    }
-
     public void testWidgetClick_positiveButton() throws Throwable {
         final String name = "Jane";
         final String phoneNumber = "456";
diff --git a/tests/robolectric/src/com/android/emergency/preferences/ContactPreferencesTest.java b/tests/robolectric/src/com/android/emergency/preferences/ContactPreferencesTest.java
index 81493f7..7a039e4 100644
--- a/tests/robolectric/src/com/android/emergency/preferences/ContactPreferencesTest.java
+++ b/tests/robolectric/src/com/android/emergency/preferences/ContactPreferencesTest.java
@@ -22,9 +22,7 @@
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
-import android.app.Instrumentation;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.ContextWrapper;
 import android.content.Intent;
 import android.content.IntentFilter;
diff --git a/tests/robolectric/src/com/android/emergency/preferences/EmergencyContactsPreferenceTest.java b/tests/robolectric/src/com/android/emergency/preferences/EmergencyContactsPreferenceTest.java
new file mode 100644
index 0000000..b13e4c3
--- /dev/null
+++ b/tests/robolectric/src/com/android/emergency/preferences/EmergencyContactsPreferenceTest.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2017 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.preferences;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.ContextWrapper;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import com.android.emergency.ContactTestUtils;
+import com.android.emergency.EmergencyContactManager;
+import com.android.emergency.PreferenceKeys;
+import com.android.emergency.TestConfig;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+
+/** Unit tests for {@link EmergencyContactsPreference}. */
+@SmallTest
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+public class EmergencyContactsPreferenceTest {
+    @Mock private PackageManager mPackageManager;
+    @Mock private PreferenceManager mPreferenceManager;
+    @Mock private SharedPreferences mSharedPreferences;
+    @Mock private EmergencyContactsPreference.ContactValidator mContactValidator;
+    @Mock private ContactPreference.ContactFactory mContactFactory;
+    private ContextWrapper mContext;
+    private EmergencyContactsPreference mPreference;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mPreferenceManager.getSharedPreferences()).thenReturn(mSharedPreferences);
+
+        mContext = spy(RuntimeEnvironment.application);
+        doReturn(mPackageManager).when(mContext).getPackageManager();
+
+        mPreference = spy(new EmergencyContactsPreference(RuntimeEnvironment.application,
+                    null /* attrs */, mContactValidator, mContactFactory));
+
+        PreferenceGroup prefRoot = spy(new PreferenceScreen(mContext, null /* attrs */));
+        when(prefRoot.getPreferenceManager()).thenReturn(mPreferenceManager);
+        prefRoot.addPreference(mPreference);
+    }
+
+    @Test
+    public void testDefaultProperties() {
+        assertThat(mPreference.isPersistent()).isTrue();
+        assertThat(mPreference.isNotSet()).isTrue();
+        assertThat(mPreference.getEmergencyContacts()).isEmpty();
+        assertThat(mPreference.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testAddAndRemoveEmergencyContact() throws Throwable {
+        final String name = "Jane";
+        final String phoneNumber = "456";
+
+        when(mContactValidator.isValidEmergencyContact(any(), any())).thenReturn(true);
+
+        EmergencyContactManager.Contact contact = mock(EmergencyContactManager.Contact.class);
+        when(mContactFactory.getContact(any(), any())).thenReturn(contact);
+        when(contact.getName()).thenReturn(name);
+        when(contact.getPhoneNumber()).thenReturn(phoneNumber);
+
+        Uri uri = mock(Uri.class);
+        when(contact.getPhoneUri()).thenReturn(uri);
+
+        mPreference.addNewEmergencyContact(uri);
+
+        assertThat(mPreference.getEmergencyContacts()).hasSize(1);
+        assertThat(mPreference.getPreferenceCount()).isEqualTo(1);
+        ContactPreference contactPreference = (ContactPreference) mPreference.getPreference(0);
+
+        assertThat(contactPreference.getPhoneUri()).isEqualTo(uri);
+        assertThat(contactPreference.getTitle()).isEqualTo(name);
+        assertThat((String) contactPreference.getSummary()).contains(phoneNumber);
+
+        mPreference.onRemoveContactPreference((ContactPreference) mPreference.getPreference(0));
+
+        assertThat(mPreference.getEmergencyContacts()).isEmpty();
+        assertThat(mPreference.getPreferenceCount()).isEqualTo(0);
+    }
+
+    @Test
+    public void testReloadFromPreference() throws Throwable {
+        final String nameJane = "Jane";
+        final String phoneNumberJane = "456";
+        Uri contactUriJane = Uri.parse("tel:" + phoneNumberJane);
+        EmergencyContactManager.Contact contactJane = mock(EmergencyContactManager.Contact.class);
+        when(mContactFactory.getContact(any(), eq(contactUriJane))).thenReturn(contactJane);
+        when(contactJane.getName()).thenReturn(nameJane);
+        when(contactJane.getPhoneNumber()).thenReturn(phoneNumberJane);
+        when(contactJane.getPhoneUri()).thenReturn(contactUriJane);
+
+        final String nameJohn = "John";
+        final String phoneNumberJohn = "123";
+        Uri contactUriJohn = Uri.parse("tel:" + phoneNumberJohn);
+        EmergencyContactManager.Contact contactJohn = mock(EmergencyContactManager.Contact.class);
+        when(mContactFactory.getContact(any(), eq(contactUriJohn))).thenReturn(contactJohn);
+        when(contactJohn.getName()).thenReturn(nameJohn);
+        when(contactJohn.getPhoneNumber()).thenReturn(phoneNumberJohn);
+        when(contactJohn.getPhoneUri()).thenReturn(contactUriJohn);
+
+        final List<Uri> emergencyContacts = new ArrayList<>();
+        emergencyContacts.add(contactUriJane);
+        emergencyContacts.add(contactUriJohn);
+        mPreference.setEmergencyContacts(emergencyContacts);
+
+        assertThat(mPreference.getEmergencyContacts().size()).isEqualTo(2);
+        assertThat(mPreference.getPreferenceCount()).isEqualTo(2);
+
+        // "Delete" Jane by reloading from preferences. The mock SharedPreferences still have both
+        // contacts stored, but the validator only believes John is valid.
+        mPreference.setKey(PreferenceKeys.KEY_EMERGENCY_CONTACTS);
+        when(mSharedPreferences.getString(eq(mPreference.getKey()), any()))
+                .thenReturn(mPreference.serialize(emergencyContacts));
+        when(mContactValidator.isValidEmergencyContact(any(), eq(contactUriJane)))
+                .thenReturn(false);
+        when(mContactValidator.isValidEmergencyContact(any(), eq(contactUriJohn))).thenReturn(true);
+        // Override the preference's persist behavior, to avoid EmergencyContactsPreference
+        // attempting to write to SharedPreferences. (Preference's default behavior is unmockable.)
+        doNothing().when(mPreference).persistEmergencyContacts(any());
+        mPreference.reloadFromPreference();
+
+        // Assert the only remaining contact is John
+        assertThat(mPreference.getEmergencyContacts()).hasSize(1);
+        assertThat(mPreference.getPreferenceCount()).isEqualTo(1);
+        ContactPreference contactPreference = (ContactPreference) mPreference.getPreference(0);
+        assertThat(contactPreference.getPhoneUri()).isEqualTo(contactUriJohn);
+    }
+}