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