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