Fixing ContactDeletionInteractionTest

Bug: 3330176
Change-Id: I6726e74440cd7cb8c16d169a97da5042f77c9463
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index c0d9240..617d5c7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -457,6 +457,12 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".test.FragmentTestActivity">
+            <intent-filter>
+                <category android:name="android.intent.category.TEST" />
+            </intent-filter>
+        </activity>
+
         <!-- Stub service used to keep our process alive long enough for
              background threads to finish their operations. -->
         <service
diff --git a/res/values/ids.xml b/res/values/ids.xml
index fd82fe2..1a553d1 100644
--- a/res/values/ids.xml
+++ b/res/values/ids.xml
@@ -37,6 +37,7 @@
 
     <!-- For ContactDeletionInteraction -->
     <item type="id" name="dialog_delete_contact_confirmation"/>
+    <item type="id" name="dialog_delete_contact_loader_id" />
 
     <!-- For ImportExportInteraction -->
     <item type="id" name="dialog_import_export_options"/>
diff --git a/src/com/android/contacts/ContactsApplication.java b/src/com/android/contacts/ContactsApplication.java
index aa1397a..8cbdfbf 100644
--- a/src/com/android/contacts/ContactsApplication.java
+++ b/src/com/android/contacts/ContactsApplication.java
@@ -17,14 +17,36 @@
 package com.android.contacts;
 
 import com.android.contacts.model.AccountTypes;
+import com.android.contacts.test.InjectedServices;
 
 import android.app.Application;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.os.StrictMode;
 import android.preference.PreferenceManager;
 
 public final class ContactsApplication extends Application {
 
+    private static InjectedServices sInjectedServices;
+
+    /**
+     * Overrides the system services with mocks for testing.
+     */
+    public static void injectContentResolver(InjectedServices services) {
+        sInjectedServices = services;
+    }
+
+    @Override
+    public ContentResolver getContentResolver() {
+        if (sInjectedServices != null) {
+            ContentResolver resolver = sInjectedServices.getContentResolver();
+            if (resolver != null) {
+                return resolver;
+            }
+        }
+        return super.getContentResolver();
+    }
+
     @Override
     public void onCreate() {
         super.onCreate();
diff --git a/src/com/android/contacts/interactions/ContactDeletionInteraction.java b/src/com/android/contacts/interactions/ContactDeletionInteraction.java
index d709bc5..654c8cf 100644
--- a/src/com/android/contacts/interactions/ContactDeletionInteraction.java
+++ b/src/com/android/contacts/interactions/ContactDeletionInteraction.java
@@ -51,8 +51,6 @@
     private static final String KEY_ACTIVE = "active";
     public static final String ARG_CONTACT_URI = "contactUri";
 
-    private static final int LOADER_ID = 0;
-
     private static final String[] ENTITY_PROJECTION = new String[] {
         Entity.RAW_CONTACT_ID, //0
         Entity.ACCOUNT_TYPE, //1
@@ -71,7 +69,10 @@
 
     private AlertDialog mDialog;
 
-    public static void start(Activity activity, Uri contactUri) {
+    // Visible for testing
+    int mMessageId;
+
+    public static ContactDeletionInteraction start(Activity activity, Uri contactUri) {
         FragmentManager fragmentManager = activity.getFragmentManager();
         ContactDeletionInteraction fragment =
                 (ContactDeletionInteraction) fragmentManager.findFragmentByTag(FRAGMENT_TAG);
@@ -82,17 +83,13 @@
         } else {
             fragment.setContactUri(contactUri);
         }
+        return fragment;
     }
 
     @Override
     public void onAttach(Activity activity) {
         super.onAttach(activity);
-        setContext(activity);
-    }
-
-    /* Visible for testing */
-    void setContext(Context context) {
-        mContext = context;
+        mContext = activity;
     }
 
     public void setContactUri(Uri contactUri) {
@@ -101,7 +98,7 @@
         if (isStarted()) {
             Bundle args = new Bundle();
             args.putParcelable(ARG_CONTACT_URI, mContactUri);
-            getLoaderManager().restartLoader(LOADER_ID, args, this);
+            getLoaderManager().restartLoader(R.id.dialog_delete_contact_loader_id, args, this);
         }
     }
 
@@ -115,7 +112,7 @@
         if (mActive) {
             Bundle args = new Bundle();
             args.putParcelable(ARG_CONTACT_URI, mContactUri);
-            getLoaderManager().initLoader(LOADER_ID, args, this);
+            getLoaderManager().initLoader(R.id.dialog_delete_contact_loader_id, args, this);
         }
         super.onStart();
     }
@@ -146,7 +143,7 @@
         HashSet<Long>  readOnlyRawContacts = Sets.newHashSet();
         HashSet<Long>  writableRawContacts = Sets.newHashSet();
 
-        AccountTypes accountTypes = getAccountTypes();
+        AccountTypes accountTypes = AccountTypes.getInstance(getActivity());
         cursor.moveToPosition(-1);
         while (cursor.moveToNext()) {
             final long rawContactId = cursor.getLong(COLUMN_INDEX_RAW_CONTACT_ID);
@@ -162,28 +159,26 @@
             }
         }
 
-        int messageId;
         int readOnlyCount = readOnlyRawContacts.size();
         int writableCount = writableRawContacts.size();
         if (readOnlyCount > 0 && writableCount > 0) {
-            messageId = R.string.readOnlyContactDeleteConfirmation;
+            mMessageId = R.string.readOnlyContactDeleteConfirmation;
         } else if (readOnlyCount > 0 && writableCount == 0) {
-            messageId = R.string.readOnlyContactWarning;
+            mMessageId = R.string.readOnlyContactWarning;
         } else if (readOnlyCount == 0 && writableCount > 1) {
-            messageId = R.string.multipleContactDeleteConfirmation;
+            mMessageId = R.string.multipleContactDeleteConfirmation;
         } else {
-            messageId = R.string.deleteConfirmation;
+            mMessageId = R.string.deleteConfirmation;
         }
 
         final Uri contactUri = Contacts.getLookupUri(contactId, lookupKey);
-        showDialog(messageId, contactUri);
+        showDialog(mMessageId, contactUri);
     }
 
     public void onLoaderReset(Loader<Cursor> loader) {
     }
 
-    /* Visible for testing */
-    void showDialog(int messageId, final Uri contactUri) {
+    private void showDialog(int messageId, final Uri contactUri) {
         mDialog = new AlertDialog.Builder(getActivity())
                 .setTitle(R.string.deleteConfirmation_title)
                 .setIcon(android.R.drawable.ic_dialog_alert)
@@ -207,7 +202,7 @@
     public void onDismiss(DialogInterface dialog) {
         mActive = false;
         mDialog = null;
-        getLoaderManager().destroyLoader(LOADER_ID);
+        getLoaderManager().destroyLoader(R.id.dialog_delete_contact_loader_id);
     }
 
     @Override
@@ -227,9 +222,4 @@
     protected void doDeleteContact(Uri contactUri) {
         mContext.startService(ContactSaveService.createDeleteContactIntent(mContext, contactUri));
     }
-
-    /* Visible for testing */
-    AccountTypes getAccountTypes() {
-        return AccountTypes.getInstance(getActivity());
-    }
 }
diff --git a/src/com/android/contacts/model/AccountTypes.java b/src/com/android/contacts/model/AccountTypes.java
index ef5265a..d9b692d 100644
--- a/src/com/android/contacts/model/AccountTypes.java
+++ b/src/com/android/contacts/model/AccountTypes.java
@@ -103,6 +103,10 @@
         return sInstance;
     }
 
+    public static void injectAccountTypes(AccountTypes injectedAccountTypes) {
+        sInstance = injectedAccountTypes;
+    }
+
     /**
      * Internal constructor that only performs initial parsing.
      */
diff --git a/src/com/android/contacts/test/FragmentTestActivity.java b/src/com/android/contacts/test/FragmentTestActivity.java
new file mode 100644
index 0000000..f95b284
--- /dev/null
+++ b/src/com/android/contacts/test/FragmentTestActivity.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 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.contacts.test;
+
+import com.android.contacts.R;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * An activity that is used for testing fragments.  A unit test starts this
+ * activity, adds a fragment and then tests the fragment.
+ */
+public class FragmentTestActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.empty);
+    }
+}
diff --git a/src/com/android/contacts/test/InjectedServices.java b/src/com/android/contacts/test/InjectedServices.java
new file mode 100644
index 0000000..f059f3a
--- /dev/null
+++ b/src/com/android/contacts/test/InjectedServices.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 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.contacts.test;
+
+import com.google.android.collect.Maps;
+
+import android.content.ContentResolver;
+
+import java.util.HashMap;
+
+/**
+ * A mechanism for providing alternative (mock) services to the application
+ * while running tests. Activities, Services and the Application should check
+ * with this class to see if a particular service has been overridden.
+ */
+public class InjectedServices {
+
+    private ContentResolver mContentResolver;
+    private HashMap<String, Object> mSystemServices;
+
+    public void setContentResolver(ContentResolver mContentResolver) {
+        this.mContentResolver = mContentResolver;
+    }
+
+    public ContentResolver getContentResolver() {
+        return mContentResolver;
+    }
+
+    public void setSystemService(String name, Object service) {
+        if (mSystemServices == null) {
+            mSystemServices = Maps.newHashMap();
+        }
+
+        mSystemServices.put(name, service);
+    }
+
+    public Object getSystemService(String name) {
+        if (mSystemServices != null) {
+            return mSystemServices.get(name);
+        }
+        return null;
+    }
+}
diff --git a/tests/src/com/android/contacts/activities/ContactBrowserActivityTest.java b/tests/src/com/android/contacts/activities/ContactBrowserActivityTest.java
index a642b56..d06f910 100644
--- a/tests/src/com/android/contacts/activities/ContactBrowserActivityTest.java
+++ b/tests/src/com/android/contacts/activities/ContactBrowserActivityTest.java
@@ -66,7 +66,7 @@
         getInstrumentation().callActivityOnResume(activity);
         getInstrumentation().callActivityOnStart(activity);
 
-        mContext.waitForLoaders(activity, R.id.contact_list_filter_loader);
+        mContext.waitForLoaders(activity.getLoaderManager(), R.id.contact_list_filter_loader);
 
         getInstrumentation().waitForIdleSync();
 
diff --git a/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java b/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
index da79311..d507741 100644
--- a/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
+++ b/tests/src/com/android/contacts/interactions/ContactDeletionInteractionTest.java
@@ -16,22 +16,22 @@
 
 package com.android.contacts.interactions;
 
+import com.android.contacts.ContactsApplication;
 import com.android.contacts.R;
 import com.android.contacts.model.AccountTypes;
+import com.android.contacts.test.FragmentTestActivity;
+import com.android.contacts.test.InjectedServices;
 import com.android.contacts.tests.mocks.ContactsMockContext;
 import com.android.contacts.tests.mocks.MockAccountTypes;
 import com.android.contacts.tests.mocks.MockContentProvider;
 import com.android.contacts.tests.mocks.MockContentProvider.Query;
-import com.android.contacts.widget.TestLoaderManager;
 
-import android.app.LoaderManager;
 import android.content.ContentUris;
-import android.content.pm.ProviderInfo;
 import android.net.Uri;
-import android.provider.ContactsContract;
+import android.os.AsyncTask;
 import android.provider.ContactsContract.Contacts;
 import android.provider.ContactsContract.Contacts.Entity;
-import android.test.InstrumentationTestCase;
+import android.test.ActivityInstrumentationTestCase2;
 import android.test.suitebuilder.annotation.Smoke;
 
 /**
@@ -45,48 +45,42 @@
  *     -w com.android.contacts.tests/android.test.InstrumentationTestRunner
  */
 @Smoke
-public class ContactDeletionInteractionTest extends InstrumentationTestCase {
+public class ContactDeletionInteractionTest
+        extends ActivityInstrumentationTestCase2<FragmentTestActivity> {
 
-    private final class TestContactDeletionDialogFragment
-            extends ContactDeletionInteraction {
-        public Uri contactUri;
-        public int messageId;
-
-        @Override
-        public LoaderManager getLoaderManager() {
-            return mLoaderManager;
-        }
-
-        @Override
-        boolean isStarted() {
-            return true;
-        }
-
-        @Override
-        void showDialog(int messageId, Uri contactUri) {
-            this.messageId = messageId;
-            this.contactUri = contactUri;
-        }
-
-        @Override
-        AccountTypes getAccountTypes() {
-            return new MockAccountTypes();
-        }
+    static {
+        // AsyncTask class needs to be initialized on the main thread.
+        AsyncTask.init();
     }
 
+    private static final Uri CONTACT_URI = ContentUris.withAppendedId(Contacts.CONTENT_URI, 13);
+    private static final Uri ENTITY_URI = Uri.withAppendedPath(
+            CONTACT_URI, Entity.CONTENT_DIRECTORY);
+
     private ContactsMockContext mContext;
     private MockContentProvider mContactsProvider;
-    private TestLoaderManager mLoaderManager;
+    private ContactDeletionInteraction mFragment;
+
+    public ContactDeletionInteractionTest() {
+        super(FragmentTestActivity.class);
+    }
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         mContext = new ContactsMockContext(getInstrumentation().getTargetContext());
+        InjectedServices services = new InjectedServices();
+        services.setContentResolver(mContext.getContentResolver());
+        ContactsApplication.injectContentResolver(services);
+        AccountTypes.injectAccountTypes(new MockAccountTypes());
         mContactsProvider = mContext.getContactsProvider();
-        ProviderInfo info = new ProviderInfo();
-        info.authority = ContactsContract.AUTHORITY;
-        mContactsProvider.attachInfo(mContext, info);
-        mLoaderManager = new TestLoaderManager();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ContactsApplication.injectContentResolver(null);
+        AccountTypes.injectAccountTypes(null);
+        super.tearDown();
     }
 
     public void testSingleWritableRawContact() {
@@ -114,20 +108,28 @@
     }
 
     private Query expectQuery() {
-        Uri uri = Uri.withAppendedPath(
-                ContentUris.withAppendedId(Contacts.CONTENT_URI, 13), Entity.CONTENT_DIRECTORY);
-        return mContactsProvider.expectQuery(uri).withProjection(
+        return mContactsProvider.expectQuery(ENTITY_URI).withProjection(
                 Entity.RAW_CONTACT_ID, Entity.ACCOUNT_TYPE, Entity.CONTACT_ID, Entity.LOOKUP_KEY);
     }
 
     private void assertWithMessageId(int messageId) {
-        TestContactDeletionDialogFragment interaction = new TestContactDeletionDialogFragment();
-        interaction.setContext(mContext);
-        interaction.setContactUri(ContentUris.withAppendedId(Contacts.CONTENT_URI, 13));
-        mLoaderManager.executeLoaders();
-        assertEquals("content://com.android.contacts/contacts/lookup/foo/13",
-                interaction.contactUri.toString());
-        assertEquals(messageId, interaction.messageId);
-        mContactsProvider.verify();
+        final FragmentTestActivity activity = getActivity();
+
+        getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mFragment = ContactDeletionInteraction.start(activity, CONTACT_URI);
+            }
+        });
+
+        getInstrumentation().waitForIdleSync();
+
+        mContext.waitForLoaders(mFragment.getLoaderManager(), R.id.dialog_delete_contact_loader_id);
+
+        getInstrumentation().waitForIdleSync();
+
+        mContext.verify();
+
+        assertEquals(messageId, mFragment.mMessageId);
     }
 }
diff --git a/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java b/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
index a7aaba4..55ba053 100644
--- a/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
+++ b/tests/src/com/android/contacts/tests/mocks/ContactsMockContext.java
@@ -18,7 +18,7 @@
 
 //import com.android.providers.contacts.ContactsMockPackageManager;
 
-import android.app.Activity;
+import android.app.LoaderManager;
 import android.content.AsyncTaskLoader;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -28,6 +28,9 @@
 import android.provider.ContactsContract;
 import android.provider.Settings;
 import android.test.mock.MockContentResolver;
+import android.util.Log;
+
+import junit.framework.Assert;
 
 /**
  * A mock context for contacts unit tests. Forwards everything to
@@ -36,6 +39,8 @@
  */
 public class ContactsMockContext extends ContextWrapper {
 
+    private static final String TAG = "ContactsMockContext";
+
     private ContactsMockPackageManager mPackageManager;
     private MockContentResolver mContentResolver;
     private MockContentProvider mContactsProvider;
@@ -84,18 +89,28 @@
     /**
      * Waits for the specified loaders to complete loading.
      */
-    public void waitForLoaders(final Activity activity, int... loaderIds) {
+    public void waitForLoaders(LoaderManager loaderManager, int... loaderIds) {
         // We want to wait for each loader using a separate thread, so that we can
         // simulate race conditions.
         Thread[] waitThreads = new Thread[loaderIds.length];
         for (int i = 0; i < loaderIds.length; i++) {
             final int loaderId = loaderIds[i];
+            final AsyncTaskLoader<?> loader =
+                    (AsyncTaskLoader<?>) loaderManager.getLoader(loaderId);
+            if (loader == null) {
+                Assert.fail("Loader does not exist: " + loaderId);
+                return;
+            }
+
             waitThreads[i] = new Thread("LoaderWaitingThread" + i) {
                 @Override
                 public void run() {
-                    AsyncTaskLoader<?> loader =
-                            (AsyncTaskLoader<?>) activity.getLoaderManager().getLoader(loaderId);
-                    loader.waitForLoader();
+                    try {
+                        loader.waitForLoader();
+                    } catch (Throwable e) {
+                        Log.e(TAG, "Exception while waiting for loader: " + loaderId, e);
+                        Assert.fail("Exception while waiting for loader: " + loaderId);
+                    }
                 }
             };
             waitThreads[i].start();