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