Unsuppress SIM import UI test

Test: ran GoogleContactsTests
Change-Id: I52e286c35a7b3f95bc2d8ed469b9e4bf473f8f64
diff --git a/src/com/android/contacts/database/SimContactDaoImpl.java b/src/com/android/contacts/database/SimContactDaoImpl.java
index 8d47824..4eb5fd3 100644
--- a/src/com/android/contacts/database/SimContactDaoImpl.java
+++ b/src/com/android/contacts/database/SimContactDaoImpl.java
@@ -89,9 +89,15 @@
     private final TelephonyManager mTelephonyManager;
 
     public SimContactDaoImpl(Context context) {
+        this(context, context.getContentResolver(),
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
+    }
+
+    public SimContactDaoImpl(Context context, ContentResolver resolver,
+            TelephonyManager telephonyManager) {
         mContext = context;
-        mResolver = context.getContentResolver();
-        mTelephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        mResolver = resolver;
+        mTelephonyManager = telephonyManager;
     }
 
     public Context getContext() {
diff --git a/tests/src/com/android/contacts/activities/SimImportActivityTest.java b/tests/src/com/android/contacts/activities/SimImportActivityTest.java
index 8362b9f..64b86e2 100644
--- a/tests/src/com/android/contacts/activities/SimImportActivityTest.java
+++ b/tests/src/com/android/contacts/activities/SimImportActivityTest.java
@@ -1,18 +1,37 @@
+/*
+ * 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.contacts.activities;
 
 import static com.android.contacts.tests.ContactsMatchers.DataCursor.hasMimeType;
 import static com.android.contacts.tests.ContactsMatchers.hasRowMatching;
 import static com.android.contacts.tests.ContactsMatchers.hasValueForColumn;
-
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.allOf;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
 
 import android.annotation.TargetApi;
 import android.app.Activity;
 import android.app.Instrumentation;
+import android.content.BroadcastReceiver;
+import android.content.ContentProviderClient;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.database.Cursor;
 import android.os.Build;
@@ -20,47 +39,54 @@
 import android.provider.ContactsContract.CommonDataKinds.Phone;
 import android.provider.ContactsContract.Data;
 import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.MediumTest;
+import android.support.test.filters.LargeTest;
 import android.support.test.filters.SdkSuppress;
-import android.support.test.filters.Suppress;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.Until;
+import android.support.v4.content.LocalBroadcastManager;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContentResolver;
 
+import com.android.contacts.SimImportService;
 import com.android.contacts.database.SimContactDao;
+import com.android.contacts.database.SimContactDaoImpl;
 import com.android.contacts.model.AccountTypeManager;
 import com.android.contacts.model.SimCard;
 import com.android.contacts.model.SimContact;
 import com.android.contacts.model.account.AccountWithDataSet;
+import com.android.contacts.test.mocks.ForwardingContentProvider;
+import com.android.contacts.test.mocks.MockContentProvider;
 import com.android.contacts.tests.AccountsTestHelper;
 import com.android.contacts.tests.ContactsMatchers;
 import com.android.contacts.tests.FakeSimContactDao;
 import com.android.contacts.tests.StringableCursor;
-
+import com.google.common.base.Function;
 import com.google.common.base.Functions;
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.SettableFuture;
 
-import org.hamcrest.BaseMatcher;
-import org.hamcrest.Description;
-import org.hamcrest.Matcher;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.TimeUnit;
+
 /**
  * UI Tests for {@link SimImportActivity}
  *
  * These should probably be converted to espresso tests because espresso does a better job of
  * waiting for the app to be idle once espresso library is added
  */
-@MediumTest
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
 @TargetApi(Build.VERSION_CODES.M)
 public class SimImportActivityTest {
 
-    public static final int TIMEOUT = 1000;
+    public static final int TIMEOUT = 3000;
     private Context mContext;
     private UiDevice mDevice;
     private Instrumentation mInstrumentation;
@@ -111,7 +137,7 @@
         assertTrue(mDevice.hasObject(By.text("Sim One")));
         assertTrue(mDevice.hasObject(By.text("Sim Two")));
         assertTrue(mDevice.hasObject(By.text("5550103")));
-}
+    }
 
     @Test
     public void shouldHaveEmptyState() {
@@ -160,27 +186,64 @@
         assertTrue(mDevice.wait(Until.hasObject(By.textStartsWith("Name One")), TIMEOUT));
     }
 
-
-    // TODO: fix this test. This doesn't work because AccountTypeManager returns a stale account
-    // list (it doesn't contain the accounts added during the current test run).
-    // Could use MockAccountTypeManager but probably ought to look at improving how
-    // AccountTypeManager updates it's account list.
-    @Suppress
+    /**
+     * Tests a complete import flow
+     *
+     * <p>Test case outline:</p>
+     * <ul>
+     * <li>Load SIM contacts
+     * <li>Change to a specific target account
+     * <li>Deselect 3 specific SIM contacts
+     * <li>Rotate the screen to landscape
+     * <li>Rotate the screen back to portrait
+     * <li>Press the import button
+     * <li>Wait for import to complete
+     * <li>Query contacts in target account and verify that they match selected contacts
+     * <li>Start import activity again
+     * <li>Switch to target account
+     * <li>Verify that previously imported contacts are disabled and not checked
+     * </ul>
+     *
+     * <p>This mocks out the IccProvider and stubs the canReadSimContacts method to make it work on
+     * an emulator but otherwise uses real dependency.
+     * </p>
+     */
     @Test
-    public void selectionsAreImportedAndDisabledOnSubsequentViews() throws Exception {
+    public void selectionsAreImportedAndDisabledOnSubsequentImports() throws Exception {
         // Clear out the instance so that it will have the most recent accounts when reloaded
         AccountTypeManager.setInstanceForTest(null);
 
         final AccountWithDataSet targetAccount = mAccountHelper.addTestAccount(
                 mAccountHelper.generateAccountName("SimImportActivity_target_"));
 
-        mDao.addSim(someSimCard(),
-                new SimContact(1, "Import One", "5550101"),
-                new SimContact(2, "Skip Two", "5550102"),
-                new SimContact(3, "Import Three", "5550103"),
-                new SimContact(4, "Skip Four", "5550104"),
-                new SimContact(5, "Skip Five", "5550105"),
-                new SimContact(6, "Import Six", "5550106"));
+        final MockContentProvider iccProvider = new MockContentProvider();
+        iccProvider.expect(MockContentProvider.Query.forAnyUri())
+                .withDefaultProjection(new String[] {SimContactDaoImpl._ID, SimContactDaoImpl.NAME,
+                        SimContactDaoImpl.NUMBER, SimContactDaoImpl.EMAILS })
+                .anyNumberOfTimes()
+                .returnRow(toCursorRow(new SimContact(1, "Import One", "5550101")))
+                .returnRow(toCursorRow(new SimContact(2, "Skip Two", "5550102")))
+                .returnRow(toCursorRow(new SimContact(3, "Import Three", "5550103")))
+                .returnRow(toCursorRow(new SimContact(4, "Skip Four", "5550104")))
+                .returnRow(toCursorRow(new SimContact(5, "Skip Five", "5550105")))
+                .returnRow(toCursorRow(new SimContact(6, "Import Six", "5550106")));
+        final MockContentResolver mockResolver = new MockContentResolver();
+        mockResolver.addProvider("icc", iccProvider);
+        final ContentProviderClient contactsProviderClient = mContext.getContentResolver()
+                .acquireContentProviderClient(ContactsContract.AUTHORITY);
+        mockResolver.addProvider(ContactsContract.AUTHORITY, new ForwardingContentProvider(
+                contactsProviderClient));
+
+        SimContactDao.setFactoryForTest(new Function<Context, SimContactDao>() {
+            @Override
+            public SimContactDao apply(Context input) {
+                final SimContactDaoImpl spy = spy(new SimContactDaoImpl(
+                        mContext, mockResolver,
+                        (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)));
+                when(spy.canReadSimContacts()).thenReturn(true);
+                return spy;
+            }
+        });
 
         mActivity = mInstrumentation.startActivitySync(
                 new Intent(mContext, SimImportActivity.class)
@@ -190,9 +253,11 @@
 
         mDevice.findObject(By.desc("Show more")).clickAndWait(Until.newWindow(), TIMEOUT);
         mDevice.findObject(By.textStartsWith("SimImportActivity_target_")).click();
-        mDevice.waitForIdle();
+
+        assertTrue(mDevice.wait(Until.hasObject(By.text("Skip Two")), TIMEOUT));
 
         mDevice.findObject(By.text("Skip Two")).click();
+        mDevice.findObject(By.text("Skip Four")).click();
         mDevice.findObject(By.text("Skip Five")).click();
         mDevice.waitForIdle();
 
@@ -204,8 +269,12 @@
         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
         mDevice.wait(Until.hasObject(By.text("Import One")), TIMEOUT);
 
+        ListenableFuture<?> nextImportFuture = nextImportCompleteBroadcast();
+
         mDevice.findObject(By.text("IMPORT").clickable(true)).click();
-        mDevice.waitForIdle();
+
+        // Block until import completes
+        nextImportFuture.get(TIMEOUT, TimeUnit.MILLISECONDS);
 
         final Cursor cursor = new StringableCursor(
                 mContext.getContentResolver().query(Data.CONTENT_URI, null,
@@ -237,37 +306,44 @@
         cursor.close();
 
 
-        mInstrumentation.startActivitySync(
+        mActivity = mInstrumentation.startActivitySync(
                 new Intent(mContext, SimImportActivity.class)
                         .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
 
         assertTrue(mDevice.wait(Until.hasObject(By.text("Import One")), TIMEOUT));
 
-        mDevice.findObject(By.descStartsWith("Saving to")).clickAndWait(Until.newWindow(), TIMEOUT);
+        mDevice.findObject(By.descStartsWith("Show more")).clickAndWait(Until.newWindow(), TIMEOUT);
         mDevice.findObject(By.textContains(targetAccount.name)).click();
         mDevice.waitForIdle();
 
-        assertTrue(mDevice.hasObject(By.text("Import One").checked(false).enabled(false)));
+        assertTrue(mDevice.wait(Until.hasObject(By.text("Import One").checked(false).enabled(false)), TIMEOUT));
         assertTrue(mDevice.hasObject(By.text("Import Three").checked(false).enabled(false)));
         assertTrue(mDevice.hasObject(By.text("Import Six").checked(false).enabled(false)));
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+            contactsProviderClient.close();
+        }
+    }
+
+    private ListenableFuture<Intent> nextImportCompleteBroadcast() {
+        final SettableFuture<Intent> result = SettableFuture.create();
+        final BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                result.set(intent);
+                LocalBroadcastManager.getInstance(mContext).unregisterReceiver(this);
+            }
+        };
+        LocalBroadcastManager.getInstance(mContext).registerReceiver(receiver, new IntentFilter(
+                SimImportService.BROADCAST_SIM_IMPORT_COMPLETE));
+        return result;
+    }
+
+    private Object[] toCursorRow(SimContact contact) {
+        return new Object[] { contact.getId(), contact.getName(), contact.getPhone(), null };
     }
 
     private SimCard someSimCard() {
         return new SimCard("id", 1, "Carrier", "SIM", "18005550101", "us");
     }
-
-    private Matcher<SimContact> withContactId(final long id) {
-        return new BaseMatcher<SimContact>() {
-            @Override
-            public boolean matches(Object o) {
-                return (o instanceof SimContact) && ((SimContact) o).getId() == id;
-            }
-
-
-            @Override
-            public void describeTo(Description description) {
-                description.appendText("Expected SimContact with id=" + id);
-            }
-        };
-    }
 }
diff --git a/tests/src/com/android/contacts/test/mocks/ForwardingContentProvider.java b/tests/src/com/android/contacts/test/mocks/ForwardingContentProvider.java
new file mode 100644
index 0000000..b6ca983
--- /dev/null
+++ b/tests/src/com/android/contacts/test/mocks/ForwardingContentProvider.java
@@ -0,0 +1,206 @@
+/*
+ * 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.contacts.test.mocks;
+
+import android.content.ContentProviderClient;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentValues;
+import android.content.OperationApplicationException;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.support.annotation.Nullable;
+
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+
+/**
+ * Forwards calls to a {@link ContentProviderClient}
+ *
+ * <p>This allows mixing use of the system content providers in a
+ * {@link android.test.mock.MockContentResolver}
+ * </p>
+ */
+public class ForwardingContentProvider extends android.test.mock.MockContentProvider {
+
+    private final ContentProviderClient mClient;
+
+    public ForwardingContentProvider(ContentProviderClient client) {
+        mClient = client;
+    }
+
+    @Override
+    public synchronized Cursor query(Uri url, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        try {
+            return mClient.query(url, projection, selection, selectionArgs, sortOrder);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Nullable
+    @Override
+    public synchronized Cursor query(Uri url, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) {
+        try {
+            return mClient.query(url, projection, selection, selectionArgs, sortOrder,
+                    cancellationSignal);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public synchronized String getType(Uri url) {
+        try {
+            return mClient.getType(url);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public synchronized String[] getStreamTypes(Uri url, String mimeTypeFilter) {
+        try {
+            return mClient.getStreamTypes(url, mimeTypeFilter);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public synchronized Uri insert(Uri url, ContentValues initialValues) {
+        try {
+            return mClient.insert(url, initialValues);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public synchronized int bulkInsert(Uri url, ContentValues[] initialValues) {
+        try {
+            return mClient.bulkInsert(url, initialValues);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public synchronized int delete(Uri url, String selection, String[] selectionArgs) {
+        try {
+            return mClient.delete(url, selection, selectionArgs);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public synchronized int update(Uri url, ContentValues values,
+            String selection, String[] selectionArgs) {
+        try {
+            return mClient.update(url, values, selection, selectionArgs);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Nullable
+    @Override
+    public synchronized ParcelFileDescriptor openFile(Uri url, String mode) {
+        try {
+            return mClient.openFile(url, mode);
+        } catch (RemoteException|FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Nullable
+    @Override
+    public synchronized ParcelFileDescriptor openFile(Uri url, String mode,
+            CancellationSignal signal) {
+        try {
+            return mClient.openFile(url, mode, signal);
+        } catch (RemoteException|FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Nullable
+    @Override
+    public synchronized AssetFileDescriptor openAssetFile(Uri url, String mode) {
+        try {
+            return mClient.openAssetFile(url, mode);
+        } catch (RemoteException|FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Nullable
+    @Override
+    public synchronized AssetFileDescriptor openAssetFile(Uri url, String mode,
+            CancellationSignal signal) {
+        try {
+            return mClient.openAssetFile(url, mode, signal);
+        } catch (RemoteException|FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public synchronized AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, String mimeType,
+            Bundle opts) {
+        try {
+            return mClient.openTypedAssetFileDescriptor(uri, mimeType, opts);
+        } catch (RemoteException|FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public synchronized AssetFileDescriptor openTypedAssetFileDescriptor(Uri uri, String mimeType,
+            Bundle opts, CancellationSignal signal) {
+        try {
+            return mClient.openTypedAssetFileDescriptor(uri, mimeType, opts, signal);
+        } catch (RemoteException|FileNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Override
+    public synchronized ContentProviderResult[] applyBatch(
+            ArrayList<ContentProviderOperation> operations) {
+        try {
+            return mClient.applyBatch(operations);
+        } catch (RemoteException|OperationApplicationException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    @Nullable
+    @Override
+    public synchronized Bundle call(String method, String arg, Bundle extras) {
+        try {
+            return mClient.call(method, arg, extras);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}