Home | History | Annotate | Download | only in activities
      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  */
     16 package com.android.contacts.activities;
     17 
     18 import static com.android.contacts.tests.ContactsMatchers.DataCursor.hasMimeType;
     19 import static com.android.contacts.tests.ContactsMatchers.hasRowMatching;
     20 import static com.android.contacts.tests.ContactsMatchers.hasValueForColumn;
     21 import static org.hamcrest.MatcherAssert.assertThat;
     22 import static org.hamcrest.Matchers.allOf;
     23 import static org.junit.Assert.assertTrue;
     24 import static org.mockito.Matchers.anyInt;
     25 import static org.mockito.Mockito.doReturn;
     26 import static org.mockito.Mockito.spy;
     27 
     28 import android.annotation.TargetApi;
     29 import android.app.Activity;
     30 import android.app.Instrumentation;
     31 import android.content.BroadcastReceiver;
     32 import android.content.ContentProviderClient;
     33 import android.content.Context;
     34 import android.content.Intent;
     35 import android.content.IntentFilter;
     36 import android.content.pm.ActivityInfo;
     37 import android.database.Cursor;
     38 import android.os.Build;
     39 import android.provider.ContactsContract;
     40 import android.provider.ContactsContract.CommonDataKinds.Phone;
     41 import android.provider.ContactsContract.Data;
     42 import android.support.test.InstrumentationRegistry;
     43 import android.support.test.filters.LargeTest;
     44 import android.support.test.filters.SdkSuppress;
     45 import android.support.test.runner.AndroidJUnit4;
     46 import android.support.test.uiautomator.By;
     47 import android.support.test.uiautomator.UiDevice;
     48 import android.support.test.uiautomator.Until;
     49 import android.support.v4.content.LocalBroadcastManager;
     50 import android.telephony.TelephonyManager;
     51 import android.test.mock.MockContentResolver;
     52 
     53 import com.android.contacts.SimImportService;
     54 import com.android.contacts.database.SimContactDao;
     55 import com.android.contacts.database.SimContactDaoImpl;
     56 import com.android.contacts.model.SimCard;
     57 import com.android.contacts.model.SimContact;
     58 import com.android.contacts.model.account.AccountWithDataSet;
     59 import com.android.contacts.test.mocks.ForwardingContentProvider;
     60 import com.android.contacts.test.mocks.MockContentProvider;
     61 import com.android.contacts.tests.AccountsTestHelper;
     62 import com.android.contacts.tests.ContactsMatchers;
     63 import com.android.contacts.tests.FakeSimContactDao;
     64 import com.android.contacts.tests.StringableCursor;
     65 import com.google.common.base.Function;
     66 import com.google.common.base.Functions;
     67 import com.google.common.util.concurrent.ListenableFuture;
     68 import com.google.common.util.concurrent.SettableFuture;
     69 
     70 import org.junit.After;
     71 import org.junit.AfterClass;
     72 import org.junit.Before;
     73 import org.junit.Test;
     74 import org.junit.runner.RunWith;
     75 
     76 import java.util.Collections;
     77 import java.util.concurrent.TimeUnit;
     78 
     79 /**
     80  * UI Tests for {@link SimImportActivity}
     81  *
     82  * These should probably be converted to espresso tests because espresso does a better job of
     83  * waiting for the app to be idle once espresso library is added
     84  */
     85 //@Suppress
     86 @LargeTest
     87 @RunWith(AndroidJUnit4.class)
     88 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
     89 @TargetApi(Build.VERSION_CODES.M)
     90 public class SimImportActivityTest {
     91 
     92     public static final int TIMEOUT = 100000;
     93     private Context mContext;
     94     private UiDevice mDevice;
     95     private Instrumentation mInstrumentation;
     96     private FakeSimContactDao mDao;
     97     private AccountsTestHelper mAccountHelper;
     98     private Activity mActivity;
     99 
    100     @Before
    101     public void setUp() throws Exception {
    102         mContext = InstrumentationRegistry.getTargetContext();
    103         mInstrumentation = InstrumentationRegistry.getInstrumentation();
    104         mDao = new FakeSimContactDao();
    105         SimContactDao.setFactoryForTest(Functions.<SimContactDao>constant(mDao));
    106         mDevice = UiDevice.getInstance(mInstrumentation);
    107 
    108         // Add some test accounts so that account picker is exercised
    109         mAccountHelper = new AccountsTestHelper();
    110         mAccountHelper.addTestAccount(mAccountHelper.generateAccountName("SimImportActivity1_"));
    111         mAccountHelper.addTestAccount(mAccountHelper.generateAccountName("SimImportActivity2_"));
    112         mAccountHelper.addTestAccount(mAccountHelper.generateAccountName("SimImportActivity3_"));
    113     }
    114 
    115     @After
    116     public void tearDown() throws Exception {
    117         SimContactDao.setFactoryForTest(SimContactDao.DEFAULT_FACTORY);
    118         mAccountHelper.cleanup();
    119         if (mActivity != null) {
    120             mActivity.finish();
    121             mInstrumentation.waitForIdleSync();
    122         }
    123     }
    124 
    125     @AfterClass
    126     public static void tearDownClass() {
    127         AccountsTestHelper.removeAccountsWithPrefix(
    128                 InstrumentationRegistry.getTargetContext(), "SimImportActivity");
    129     }
    130 
    131     @Test
    132     public void shouldDisplaySimContacts() {
    133         mDao.addSim(someSimCard(),
    134                         new SimContact(1, "Sim One", "5550101"),
    135                         new SimContact(2, "Sim Two", null),
    136                         new SimContact(3, null, "5550103")
    137                 );
    138         mActivity = mInstrumentation.startActivitySync(new Intent(mContext, SimImportActivity.class)
    139                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    140 
    141         mDevice.waitForIdle();
    142 
    143         assertTrue(mDevice.wait(Until.hasObject(By.text("Sim One")), TIMEOUT));
    144 
    145         assertTrue(mDevice.hasObject(By.text("Sim One")));
    146         assertTrue(mDevice.hasObject(By.text("Sim Two")));
    147         assertTrue(mDevice.hasObject(By.text("5550103")));
    148     }
    149 
    150     @Test
    151     public void shouldHaveEmptyState() {
    152         mDao.addSim(someSimCard());
    153 
    154         mInstrumentation.startActivitySync(new Intent(mContext, SimImportActivity.class)
    155                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    156 
    157         mDevice.waitForIdle();
    158 
    159         assertTrue(mDevice.wait(Until.hasObject(By.textStartsWith("No contacts")), TIMEOUT));
    160     }
    161 
    162     @Test
    163     public void smokeRotateInEmptyState() {
    164         mDao.addSim(someSimCard());
    165 
    166         mActivity = mInstrumentation.startActivitySync(
    167                 new Intent(mContext, SimImportActivity.class)
    168                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    169 
    170         assertTrue(mDevice.wait(Until.hasObject(By.textStartsWith("No contacts")), TIMEOUT));
    171 
    172         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    173 
    174         mDevice.waitForIdle();
    175 
    176         assertTrue(mDevice.wait(Until.hasObject(By.textStartsWith("No contacts")), TIMEOUT));
    177     }
    178 
    179     @Test
    180     public void smokeRotateInNonEmptyState() throws Exception {
    181         mDao.addSim(someSimCard(), new SimContact(1, "Name One", "5550101"),
    182                 new SimContact(2, "Name Two", "5550102"));
    183 
    184         mActivity = mInstrumentation.startActivitySync(
    185                 new Intent(mContext, SimImportActivity.class)
    186                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    187 
    188         assertTrue(mDevice.wait(Until.hasObject(By.textStartsWith("Name One")), TIMEOUT));
    189 
    190         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    191 
    192         mDevice.waitForIdle();
    193 
    194         assertTrue(mDevice.wait(Until.hasObject(By.textStartsWith("Name One")), TIMEOUT));
    195     }
    196 
    197     /**
    198      * Tests a complete import flow
    199      *
    200      * <p>Test case outline:</p>
    201      * <ul>
    202      * <li>Load SIM contacts
    203      * <li>Change to a specific target account
    204      * <li>Deselect 3 specific SIM contacts
    205      * <li>Rotate the screen to landscape
    206      * <li>Rotate the screen back to portrait
    207      * <li>Press the import button
    208      * <li>Wait for import to complete
    209      * <li>Query contacts in target account and verify that they match selected contacts
    210      * <li>Start import activity again
    211      * <li>Switch to target account
    212      * <li>Verify that previously imported contacts are disabled and not checked
    213      * </ul>
    214      *
    215      * <p>This mocks out the IccProvider and stubs the canReadSimContacts method to make it work on
    216      * an emulator but otherwise uses real dependency.
    217      * </p>
    218      */
    219     @Test
    220     public void selectionsAreImportedAndDisabledOnSubsequentImports() throws Exception {
    221         final AccountWithDataSet targetAccount = mAccountHelper.addTestAccount(
    222                 mAccountHelper.generateAccountName("SimImportActivity0_targetAccount_"));
    223 
    224         final MockContentProvider iccProvider = new MockContentProvider();
    225         iccProvider.expect(MockContentProvider.Query.forAnyUri())
    226                 .withDefaultProjection(new String[] {SimContactDaoImpl._ID, SimContactDaoImpl.NAME,
    227                         SimContactDaoImpl.NUMBER, SimContactDaoImpl.EMAILS })
    228                 .anyNumberOfTimes()
    229                 .returnRow(toCursorRow(new SimContact(1, "Import One", "5550101")))
    230                 .returnRow(toCursorRow(new SimContact(2, "Skip Two", "5550102")))
    231                 .returnRow(toCursorRow(new SimContact(3, "Import Three", "5550103")))
    232                 .returnRow(toCursorRow(new SimContact(4, "Skip Four", "5550104")))
    233                 .returnRow(toCursorRow(new SimContact(5, "Skip Five", "5550105")))
    234                 .returnRow(toCursorRow(new SimContact(6, "Import Six", "5550106")));
    235         final MockContentResolver mockResolver = new MockContentResolver();
    236         mockResolver.addProvider("icc", iccProvider);
    237         final ContentProviderClient contactsProviderClient = mContext.getContentResolver()
    238                 .acquireContentProviderClient(ContactsContract.AUTHORITY);
    239         mockResolver.addProvider(ContactsContract.AUTHORITY, new ForwardingContentProvider(
    240                 contactsProviderClient));
    241 
    242         SimContactDao.setFactoryForTest(new Function<Context, SimContactDao>() {
    243             @Override
    244             public SimContactDao apply(Context input) {
    245                 final SimContactDaoImpl spy = spy(new SimContactDaoImpl(
    246                         mContext, mockResolver,
    247                         (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE)));
    248                 final SimCard sim = someSimCard();
    249                 doReturn(true).when(spy).canReadSimContacts();
    250                 doReturn(Collections.singletonList(sim)).when(spy).getSimCards();
    251                 doReturn(sim).when(spy).getSimBySubscriptionId(anyInt());
    252                 return spy;
    253             }
    254         });
    255 
    256         mActivity = mInstrumentation.startActivitySync(
    257                 new Intent(mContext, SimImportActivity.class)
    258                         .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    259 
    260         assertTrue(mDevice.wait(Until.hasObject(By.desc("Show more")), TIMEOUT));
    261 
    262         mDevice.findObject(By.desc("Show more")).clickAndWait(Until.newWindow(), TIMEOUT);
    263         mDevice.findObject(By.textContains("_targetAccount_")).click();
    264 
    265         assertTrue(mDevice.wait(Until.hasObject(By.text("Skip Two")), TIMEOUT));
    266 
    267         mDevice.findObject(By.text("Skip Two")).click();
    268         mDevice.findObject(By.text("Skip Four")).click();
    269         mDevice.findObject(By.text("Skip Five")).click();
    270         mDevice.waitForIdle();
    271 
    272         assertTrue(mDevice.hasObject(By.text("Skip Two").checked(false)));
    273         assertTrue(mDevice.hasObject(By.text("Skip Five").checked(false)));
    274 
    275         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    276         mDevice.wait(Until.hasObject(By.text("Import One")), TIMEOUT);
    277         mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_USER);
    278         mDevice.wait(Until.hasObject(By.text("Import One")), TIMEOUT);
    279 
    280         ListenableFuture<?> nextImportFuture = nextImportCompleteBroadcast();
    281 
    282         mDevice.findObject(By.text("IMPORT").clickable(true)).click();
    283 
    284         // Block until import completes
    285         nextImportFuture.get(TIMEOUT, TimeUnit.MILLISECONDS);
    286 
    287         final Cursor cursor = new StringableCursor(
    288                 mContext.getContentResolver().query(Data.CONTENT_URI, null,
    289                         ContactsContract.RawContacts.ACCOUNT_NAME + "=? AND " +
    290                                 ContactsContract.RawContacts.ACCOUNT_TYPE+ "=?",
    291                         new String[] {
    292                                 targetAccount.name,
    293                                 targetAccount.type
    294                         }, null));
    295         // 3 contacts imported with one row for name and one for phone
    296         assertThat(cursor, ContactsMatchers.hasCount(3 * 2));
    297 
    298         assertThat(cursor, hasRowMatching(allOf(
    299                 hasMimeType(Phone.CONTENT_ITEM_TYPE),
    300                 hasValueForColumn(Phone.DISPLAY_NAME, "Import One"),
    301                 hasValueForColumn(Phone.NUMBER, "5550101")
    302         )));
    303         assertThat(cursor, hasRowMatching(allOf(
    304                 hasMimeType(Phone.CONTENT_ITEM_TYPE),
    305                 hasValueForColumn(Phone.DISPLAY_NAME, "Import Three"),
    306                 hasValueForColumn(Phone.NUMBER, "5550103")
    307         )));
    308         assertThat(cursor, hasRowMatching(allOf(
    309                 hasMimeType(Phone.CONTENT_ITEM_TYPE),
    310                 hasValueForColumn(Phone.DISPLAY_NAME, "Import Six"),
    311                 hasValueForColumn(Phone.NUMBER, "5550106")
    312         )));
    313 
    314         cursor.close();
    315 
    316 
    317         mActivity = mInstrumentation.startActivitySync(
    318                 new Intent(mContext, SimImportActivity.class)
    319                         .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    320 
    321         assertTrue(mDevice.wait(Until.hasObject(By.text("Import One")), TIMEOUT));
    322 
    323         mDevice.findObject(By.descStartsWith("Show more")).clickAndWait(Until.newWindow(), TIMEOUT);
    324         mDevice.findObject(By.textContains(targetAccount.name)).click();
    325         mDevice.waitForIdle();
    326 
    327         assertTrue(mDevice.wait(Until.hasObject(By.text("Import One").checked(false).enabled(false)), TIMEOUT));
    328         assertTrue(mDevice.hasObject(By.text("Import Three").checked(false).enabled(false)));
    329         assertTrue(mDevice.hasObject(By.text("Import Six").checked(false).enabled(false)));
    330 
    331         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    332             contactsProviderClient.close();
    333         }
    334     }
    335 
    336     private ListenableFuture<Intent> nextImportCompleteBroadcast() {
    337         final SettableFuture<Intent> result = SettableFuture.create();
    338         final BroadcastReceiver receiver = new BroadcastReceiver() {
    339             @Override
    340             public void onReceive(Context context, Intent intent) {
    341                 result.set(intent);
    342                 LocalBroadcastManager.getInstance(mContext).unregisterReceiver(this);
    343             }
    344         };
    345         LocalBroadcastManager.getInstance(mContext).registerReceiver(receiver, new IntentFilter(
    346                 SimImportService.BROADCAST_SIM_IMPORT_COMPLETE));
    347         return result;
    348     }
    349 
    350     private Object[] toCursorRow(SimContact contact) {
    351         return new Object[] { contact.getId(), contact.getName(), contact.getPhone(), null };
    352     }
    353 
    354     private SimCard someSimCard() {
    355         return new SimCard("id", 1, "Carrier", "SIM", "18005550101", "us");
    356     }
    357 }
    358