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 17 package android.telephony.cts; 18 19 import static androidx.test.InstrumentationRegistry.getInstrumentation; 20 21 import static junit.framework.Assert.assertNotNull; 22 23 import static org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assert.fail; 26 27 import android.app.Instrumentation; 28 import android.content.BroadcastReceiver; 29 import android.content.ComponentName; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.content.pm.PackageManager; 34 import android.database.ContentObserver; 35 import android.database.Cursor; 36 import android.database.sqlite.SQLiteException; 37 import android.net.Uri; 38 import android.os.Handler; 39 import android.os.Looper; 40 import android.os.ParcelFileDescriptor; 41 import android.provider.Telephony.Sms; 42 import android.provider.Telephony.Sms.Intents; 43 import androidx.annotation.Nullable; 44 import android.telecom.PhoneAccount; 45 import android.telecom.PhoneAccountHandle; 46 import android.telecom.TelecomManager; 47 import android.telephony.SmsManager; 48 import android.telephony.SmsMessage; 49 import android.telephony.TelephonyManager; 50 import android.telephony.VisualVoicemailSms; 51 import android.telephony.VisualVoicemailSmsFilterSettings; 52 import android.text.TextUtils; 53 import android.util.Log; 54 55 import java.io.BufferedReader; 56 import java.io.FileInputStream; 57 import java.io.InputStream; 58 import java.io.InputStreamReader; 59 import java.nio.ByteBuffer; 60 import java.nio.charset.CharacterCodingException; 61 import java.nio.charset.CharsetDecoder; 62 import java.nio.charset.StandardCharsets; 63 import java.util.Arrays; 64 import java.util.concurrent.CompletableFuture; 65 import java.util.concurrent.ExecutionException; 66 import java.util.concurrent.TimeUnit; 67 import java.util.concurrent.TimeoutException; 68 69 import org.junit.After; 70 import org.junit.Before; 71 import org.junit.Test; 72 73 public class VisualVoicemailServiceTest { 74 75 private static final String TAG = "VvmServiceTest"; 76 77 private static final String COMMAND_SET_DEFAULT_DIALER = "telecom set-default-dialer "; 78 79 private static final String COMMAND_GET_DEFAULT_DIALER = "telecom get-default-dialer"; 80 81 private static final String PACKAGE = "android.telephony.cts"; 82 83 private static final long EVENT_RECEIVED_TIMEOUT_MILLIS = 60_000; 84 private static final long EVENT_NOT_RECEIVED_TIMEOUT_MILLIS = 1_000; 85 86 private Context mContext; 87 private TelephonyManager mTelephonyManager; 88 89 private String mPreviousDefaultDialer; 90 91 private PhoneAccountHandle mPhoneAccountHandle; 92 private String mPhoneNumber; 93 94 private SmsBroadcastReceiver mSmsReceiver; 95 96 @Before setUp()97 public void setUp() throws Exception { 98 mContext = getInstrumentation().getContext(); 99 if (hasTelephony(mContext)) { 100 mPreviousDefaultDialer = getDefaultDialer(getInstrumentation()); 101 setDefaultDialer(getInstrumentation(), PACKAGE); 102 103 TelecomManager telecomManager = mContext.getSystemService(TelecomManager.class); 104 mPhoneAccountHandle = telecomManager 105 .getDefaultOutgoingPhoneAccount(PhoneAccount.SCHEME_TEL); 106 assertNotNull(mPhoneAccountHandle); 107 mPhoneNumber = telecomManager.getLine1Number(mPhoneAccountHandle); 108 assertNotNull(mPhoneNumber, "Tests require a line1 number for the active SIM."); 109 110 mTelephonyManager = mContext.getSystemService(TelephonyManager.class) 111 .createForPhoneAccountHandle(mPhoneAccountHandle); 112 } 113 114 PackageManager packageManager = mContext.getPackageManager(); 115 packageManager.setComponentEnabledSetting( 116 new ComponentName(mContext, MockVisualVoicemailService.class), 117 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); 118 packageManager.setComponentEnabledSetting( 119 new ComponentName(mContext, PermissionlessVisualVoicemailService.class), 120 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); 121 } 122 123 @After tearDown()124 public void tearDown() throws Exception { 125 if (hasTelephony(mContext)) { 126 if (!TextUtils.isEmpty(mPreviousDefaultDialer)) { 127 setDefaultDialer(getInstrumentation(), mPreviousDefaultDialer); 128 } 129 130 if (mSmsReceiver != null) { 131 mContext.unregisterReceiver(mSmsReceiver); 132 } 133 } 134 } 135 136 @Test testPermissionlessService_ignored()137 public void testPermissionlessService_ignored() { 138 if (!hasTelephony(mContext)) { 139 Log.d(TAG, "skipping test that requires telephony feature"); 140 return; 141 } 142 143 PackageManager packageManager = mContext.getPackageManager(); 144 packageManager.setComponentEnabledSetting( 145 new ComponentName(mContext, MockVisualVoicemailService.class), 146 PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP); 147 packageManager.setComponentEnabledSetting( 148 new ComponentName(mContext, PermissionlessVisualVoicemailService.class), 149 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP); 150 String clientPrefix = "//CTSVVM"; 151 String text = "//CTSVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1"; 152 153 mTelephonyManager.setVisualVoicemailSmsFilterSettings( 154 new VisualVoicemailSmsFilterSettings.Builder() 155 .setClientPrefix(clientPrefix) 156 .build()); 157 158 try { 159 mTelephonyManager 160 .sendVisualVoicemailSms(mPhoneNumber, 0, text, null); 161 fail("SecurityException expected"); 162 } catch (SecurityException e) { 163 // Expected 164 } 165 166 CompletableFuture<VisualVoicemailSms> future = new CompletableFuture<>(); 167 PermissionlessVisualVoicemailService.setSmsFuture(future); 168 169 setupSmsReceiver(text); 170 171 SmsManager.getDefault().sendTextMessage(mPhoneNumber, null, text, null, null); 172 173 mSmsReceiver.assertReceived(EVENT_RECEIVED_TIMEOUT_MILLIS); 174 try { 175 future.get(EVENT_NOT_RECEIVED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 176 throw new RuntimeException("Unexpected visual voicemail SMS received"); 177 } catch (TimeoutException e) { 178 // expected 179 } catch (ExecutionException | InterruptedException e) { 180 throw new RuntimeException(e); 181 } 182 183 } 184 185 @Test testFilter()186 public void testFilter() { 187 if (!hasTelephony(mContext)) { 188 Log.d(TAG, "skipping test that requires telephony feature"); 189 return; 190 } 191 VisualVoicemailSms result = getSmsFromText("//CTSVVM", 192 "//CTSVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1"); 193 194 assertEquals("STATUS", result.getPrefix()); 195 assertEquals("R", result.getFields().getString("st")); 196 assertEquals("0", result.getFields().getString("rc")); 197 assertEquals("1", result.getFields().getString("srv")); 198 assertEquals("1", result.getFields().getString("dn")); 199 assertEquals("1", result.getFields().getString("ipt")); 200 assertEquals("0", result.getFields().getString("spt")); 201 assertEquals("eg@example.com", result.getFields().getString("u")); 202 assertEquals("1", result.getFields().getString("pw")); 203 } 204 205 @Test testFilter_data()206 public void testFilter_data() { 207 if (!hasTelephony(mContext)) { 208 Log.d(TAG, "skipping test that requires telephony feature"); 209 return; 210 } 211 if (!hasDataSms()) { 212 Log.d(TAG, "skipping test that requires data SMS feature"); 213 return; 214 } 215 216 VisualVoicemailSmsFilterSettings settings = new VisualVoicemailSmsFilterSettings.Builder() 217 .setClientPrefix("//CTSVVM") 218 .build(); 219 VisualVoicemailSms result = getSmsFromData(settings, (short) 1000, 220 "//CTSVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1", true); 221 222 assertEquals("STATUS", result.getPrefix()); 223 assertEquals("R", result.getFields().getString("st")); 224 assertEquals("0", result.getFields().getString("rc")); 225 assertEquals("1", result.getFields().getString("srv")); 226 assertEquals("1", result.getFields().getString("dn")); 227 assertEquals("1", result.getFields().getString("ipt")); 228 assertEquals("0", result.getFields().getString("spt")); 229 assertEquals("eg@example.com", result.getFields().getString("u")); 230 assertEquals("1", result.getFields().getString("pw")); 231 } 232 233 234 @Test testFilter_TrailingSemiColon()235 public void testFilter_TrailingSemiColon() { 236 if (!hasTelephony(mContext)) { 237 Log.d(TAG, "skipping test that requires telephony feature"); 238 return; 239 } 240 VisualVoicemailSms result = getSmsFromText("//CTSVVM", 241 "//CTSVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1;"); 242 243 assertEquals("STATUS", result.getPrefix()); 244 assertEquals("R", result.getFields().getString("st")); 245 assertEquals("0", result.getFields().getString("rc")); 246 assertEquals("1", result.getFields().getString("srv")); 247 assertEquals("1", result.getFields().getString("dn")); 248 assertEquals("1", result.getFields().getString("ipt")); 249 assertEquals("0", result.getFields().getString("spt")); 250 assertEquals("eg@example.com", result.getFields().getString("u")); 251 assertEquals("1", result.getFields().getString("pw")); 252 } 253 254 @Test testFilter_EmptyPrefix()255 public void testFilter_EmptyPrefix() { 256 if (!hasTelephony(mContext)) { 257 Log.d(TAG, "skipping test that requires telephony feature"); 258 return; 259 } 260 VisualVoicemailSms result = getSmsFromText("//CTSVVM", 261 "//CTSVVM::st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1"); 262 263 assertEquals("", result.getPrefix()); 264 assertEquals("R", result.getFields().getString("st")); 265 assertEquals("0", result.getFields().getString("rc")); 266 assertEquals("1", result.getFields().getString("srv")); 267 assertEquals("1", result.getFields().getString("dn")); 268 assertEquals("1", result.getFields().getString("ipt")); 269 assertEquals("0", result.getFields().getString("spt")); 270 assertEquals("eg@example.com", result.getFields().getString("u")); 271 assertEquals("1", result.getFields().getString("pw")); 272 } 273 274 @Test testFilter_EmptyField()275 public void testFilter_EmptyField() { 276 if (!hasTelephony(mContext)) { 277 Log.d(TAG, "skipping test that requires telephony feature"); 278 return; 279 } 280 VisualVoicemailSms result = getSmsFromText("//CTSVVM", 281 "//CTSVVM:STATUS:"); 282 assertTrue(result.getFields().isEmpty()); 283 } 284 285 @Test testFilterFail_NotVvm()286 public void testFilterFail_NotVvm() { 287 if (!hasTelephony(mContext)) { 288 Log.d(TAG, "skipping test that requires telephony feature"); 289 return; 290 } 291 assertVisualVoicemailSmsNotReceived("//CTSVVM", 292 "helloworld"); 293 } 294 295 @Test testFilterFail_PrefixMismatch()296 public void testFilterFail_PrefixMismatch() { 297 if (!hasTelephony(mContext)) { 298 Log.d(TAG, "skipping test that requires telephony feature"); 299 return; 300 } 301 assertVisualVoicemailSmsNotReceived("//CTSVVM", 302 "//FOOVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1"); 303 } 304 305 @Test testFilterFail_MissingFirstColon()306 public void testFilterFail_MissingFirstColon() { 307 if (!hasTelephony(mContext)) { 308 Log.d(TAG, "skipping test that requires telephony feature"); 309 return; 310 } 311 assertVisualVoicemailSmsNotReceived("//CTSVVM", 312 "//CTSVVMSTATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1"); 313 } 314 315 @Test testFilterFail_MissingSecondColon()316 public void testFilterFail_MissingSecondColon() { 317 if (!hasTelephony(mContext)) { 318 Log.d(TAG, "skipping test that requires telephony feature"); 319 return; 320 } 321 assertVisualVoicemailSmsNotReceived("//CTSVVM", 322 "//CTSVVM:STATUSst=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1"); 323 } 324 325 @Test testFilterFail_MessageEndAfterClientPrefix()326 public void testFilterFail_MessageEndAfterClientPrefix() { 327 if (!hasTelephony(mContext)) { 328 Log.d(TAG, "skipping test that requires telephony feature"); 329 return; 330 } 331 assertVisualVoicemailSmsNotReceived("//CTSVVM", 332 "//CTSVVM:"); 333 } 334 335 @Test testFilterFail_MessageEndAfterPrefix()336 public void testFilterFail_MessageEndAfterPrefix() { 337 if (!hasTelephony(mContext)) { 338 Log.d(TAG, "skipping test that requires telephony feature"); 339 return; 340 } 341 assertVisualVoicemailSmsNotReceived("//CTSVVM", 342 "//CTSVVM:STATUS"); 343 } 344 345 @Test testFilterFail_InvalidKeyValuePair()346 public void testFilterFail_InvalidKeyValuePair() { 347 if (!hasTelephony(mContext)) { 348 Log.d(TAG, "skipping test that requires telephony feature"); 349 return; 350 } 351 assertVisualVoicemailSmsNotReceived("//CTSVVM", 352 "//CTSVVM:STATUS:key"); 353 } 354 355 @Test testFilterFail_InvalidMissingKey()356 public void testFilterFail_InvalidMissingKey() { 357 if (!hasTelephony(mContext)) { 358 Log.d(TAG, "skipping test that requires telephony feature"); 359 return; 360 } 361 assertVisualVoicemailSmsNotReceived("//CTSVVM", 362 "//CTSVVM:STATUS:=value"); 363 } 364 365 @Test testFilter_MissingValue()366 public void testFilter_MissingValue() { 367 if (!hasTelephony(mContext)) { 368 Log.d(TAG, "skipping test that requires telephony feature"); 369 return; 370 } 371 VisualVoicemailSms result = getSmsFromText("//CTSVVM", 372 "//CTSVVM:STATUS:key="); 373 assertEquals("STATUS", result.getPrefix()); 374 assertEquals("", result.getFields().getString("key")); 375 } 376 377 @Test testFilter_originatingNumber_match_filtered()378 public void testFilter_originatingNumber_match_filtered() { 379 if (!hasTelephony(mContext)) { 380 Log.d(TAG, "skipping test that requires telephony feature"); 381 return; 382 } 383 VisualVoicemailSmsFilterSettings settings = new VisualVoicemailSmsFilterSettings.Builder() 384 .setClientPrefix("//CTSVVM") 385 .setOriginatingNumbers(Arrays.asList(mPhoneNumber)) 386 .build(); 387 388 getSmsFromText(settings, "//CTSVVM:SYNC:key=value", true); 389 } 390 391 @Test testFilter_originatingNumber_mismatch_notFiltered()392 public void testFilter_originatingNumber_mismatch_notFiltered() { 393 if (!hasTelephony(mContext)) { 394 Log.d(TAG, "skipping test that requires telephony feature"); 395 return; 396 } 397 VisualVoicemailSmsFilterSettings settings = new VisualVoicemailSmsFilterSettings.Builder() 398 .setClientPrefix("//CTSVVM") 399 .setOriginatingNumbers(Arrays.asList("1")) 400 .build(); 401 402 getSmsFromText(settings, "//CTSVVM:SYNC:key=value", false); 403 } 404 405 @Test testFilter_port_match()406 public void testFilter_port_match() { 407 if (!hasTelephony(mContext)) { 408 Log.d(TAG, "skipping test that requires telephony feature"); 409 return; 410 } 411 if (!hasDataSms()) { 412 Log.d(TAG, "skipping test that requires data SMS feature"); 413 return; 414 } 415 416 VisualVoicemailSmsFilterSettings settings = new VisualVoicemailSmsFilterSettings.Builder() 417 .setClientPrefix("//CTSVVM") 418 .setDestinationPort(1000) 419 .build(); 420 getSmsFromData(settings, (short) 1000, 421 "//CTSVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1", true); 422 } 423 424 @Test testFilter_port_mismatch()425 public void testFilter_port_mismatch() { 426 if (!hasTelephony(mContext)) { 427 Log.d(TAG, "skipping test that requires telephony feature"); 428 return; 429 } 430 if (!hasDataSms()) { 431 Log.d(TAG, "skipping test that requires data SMS feature"); 432 return; 433 } 434 435 VisualVoicemailSmsFilterSettings settings = new VisualVoicemailSmsFilterSettings.Builder() 436 .setClientPrefix("//CTSVVM") 437 .setDestinationPort(1001) 438 .build(); 439 getSmsFromData(settings, (short) 1000, 440 "//CTSVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1", false); 441 } 442 443 @Test testFilter_port_anydata()444 public void testFilter_port_anydata() { 445 if (!hasTelephony(mContext)) { 446 Log.d(TAG, "skipping test that requires telephony feature"); 447 return; 448 } 449 if (!hasDataSms()) { 450 Log.d(TAG, "skipping test that requires data SMS feature"); 451 return; 452 } 453 454 VisualVoicemailSmsFilterSettings settings = new VisualVoicemailSmsFilterSettings.Builder() 455 .setClientPrefix("//CTSVVM") 456 .setDestinationPort(VisualVoicemailSmsFilterSettings.DESTINATION_PORT_DATA_SMS) 457 .build(); 458 getSmsFromData(settings, (short) 1000, 459 "//CTSVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1", true); 460 } 461 462 /** 463 * Text SMS should not be filtered with DESTINATION_PORT_DATA_SMS 464 */ 465 @Test testFilter_port_anydata_notData()466 public void testFilter_port_anydata_notData() { 467 if (!hasTelephony(mContext)) { 468 Log.d(TAG, "skipping test that requires telephony feature"); 469 return; 470 } 471 if (!hasDataSms()) { 472 Log.d(TAG, "skipping test that requires data SMS feature"); 473 return; 474 } 475 476 VisualVoicemailSmsFilterSettings settings = new VisualVoicemailSmsFilterSettings.Builder() 477 .setClientPrefix("//CTSVVM") 478 .setDestinationPort(VisualVoicemailSmsFilterSettings.DESTINATION_PORT_DATA_SMS) 479 .build(); 480 getSmsFromText(settings, 481 "//CTSVVM:STATUS:st=R;rc=0;srv=1;dn=1;ipt=1;spt=0;u=eg@example.com;pw=1", false); 482 } 483 484 @Test testGetVisualVoicemailPackageName_isSelf()485 public void testGetVisualVoicemailPackageName_isSelf() { 486 if (!hasTelephony(mContext)) { 487 Log.d(TAG, "skipping test that requires telephony feature"); 488 return; 489 } 490 assertEquals(PACKAGE, mTelephonyManager.getVisualVoicemailPackageName()); 491 } 492 getSmsFromText(String clientPrefix, String text)493 private VisualVoicemailSms getSmsFromText(String clientPrefix, String text) { 494 return getSmsFromText(clientPrefix, text, true); 495 } 496 497 @Nullable getSmsFromText(String clientPrefix, String text, boolean expectVvmSms)498 private VisualVoicemailSms getSmsFromText(String clientPrefix, String text, 499 boolean expectVvmSms) { 500 return getSmsFromText( 501 new VisualVoicemailSmsFilterSettings.Builder() 502 .setClientPrefix(clientPrefix) 503 .build(), 504 text, 505 expectVvmSms); 506 } 507 assertVisualVoicemailSmsNotReceived(String clientPrefix, String text)508 private void assertVisualVoicemailSmsNotReceived(String clientPrefix, String text) { 509 getSmsFromText(clientPrefix, text, false); 510 } 511 512 /** 513 * Setup the SMS filter with only the {@code clientPrefix}, and sends {@code text} to the 514 * device. The SMS sent should not be written to the SMS provider. <p> If {@code expectVvmSms} 515 * is {@code true}, the SMS should be be caught by the SMS filter. The user should not receive 516 * the text, and the parsed result will be returned.* <p> If {@code expectVvmSms} is {@code 517 * false}, the SMS should pass through the SMS filter. The user should receive the text, and 518 * {@code null} be returned. 519 */ 520 @Nullable getSmsFromText(VisualVoicemailSmsFilterSettings settings, String text, boolean expectVvmSms)521 private VisualVoicemailSms getSmsFromText(VisualVoicemailSmsFilterSettings settings, 522 String text, 523 boolean expectVvmSms) { 524 525 mTelephonyManager.setVisualVoicemailSmsFilterSettings(settings); 526 527 CompletableFuture<VisualVoicemailSms> future = new CompletableFuture<>(); 528 MockVisualVoicemailService.setSmsFuture(future); 529 530 setupSmsReceiver(text); 531 try (SentSmsObserver observer = new SentSmsObserver(mContext, text)) { 532 mTelephonyManager 533 .sendVisualVoicemailSms(mPhoneNumber,0, text, null); 534 535 if (expectVvmSms) { 536 VisualVoicemailSms sms; 537 try { 538 sms = future.get(EVENT_RECEIVED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 539 } catch (InterruptedException | ExecutionException | TimeoutException e) { 540 throw new RuntimeException(e); 541 } 542 mSmsReceiver.assertNotReceived(EVENT_NOT_RECEIVED_TIMEOUT_MILLIS); 543 observer.assertNotChanged(); 544 return sms; 545 } else { 546 mSmsReceiver.assertReceived(EVENT_RECEIVED_TIMEOUT_MILLIS); 547 try { 548 future.get(EVENT_NOT_RECEIVED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 549 throw new RuntimeException("Unexpected visual voicemail SMS received"); 550 } catch (TimeoutException e) { 551 // expected 552 return null; 553 } catch (ExecutionException | InterruptedException e) { 554 throw new RuntimeException(e); 555 } 556 } 557 } 558 } 559 560 @Nullable getSmsFromData(VisualVoicemailSmsFilterSettings settings, short port, String text, boolean expectVvmSms)561 private VisualVoicemailSms getSmsFromData(VisualVoicemailSmsFilterSettings settings, short port, 562 String text, boolean expectVvmSms) { 563 564 mTelephonyManager.setVisualVoicemailSmsFilterSettings(settings); 565 566 CompletableFuture<VisualVoicemailSms> future = new CompletableFuture<>(); 567 MockVisualVoicemailService.setSmsFuture(future); 568 569 setupSmsReceiver(text); 570 mTelephonyManager.sendVisualVoicemailSms(mPhoneNumber, port, text, null); 571 572 if (expectVvmSms) { 573 VisualVoicemailSms sms; 574 try { 575 sms = future.get(EVENT_RECEIVED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 576 } catch (InterruptedException | ExecutionException | TimeoutException e) { 577 throw new RuntimeException(e); 578 } 579 mSmsReceiver.assertNotReceived(EVENT_NOT_RECEIVED_TIMEOUT_MILLIS); 580 return sms; 581 } else { 582 mSmsReceiver.assertReceived(EVENT_RECEIVED_TIMEOUT_MILLIS); 583 try { 584 future.get(EVENT_NOT_RECEIVED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 585 throw new RuntimeException("Unexpected visual voicemail SMS received"); 586 } catch (TimeoutException e) { 587 // expected 588 return null; 589 } catch (ExecutionException | InterruptedException e) { 590 throw new RuntimeException(e); 591 } 592 } 593 } 594 setupSmsReceiver(String text)595 private void setupSmsReceiver(String text) { 596 mSmsReceiver = new SmsBroadcastReceiver(text); 597 mContext.registerReceiver(mSmsReceiver, new IntentFilter(Intents.SMS_RECEIVED_ACTION)); 598 IntentFilter dataFilter = new IntentFilter(Intents.DATA_SMS_RECEIVED_ACTION); 599 dataFilter.addDataScheme("sms"); 600 mContext.registerReceiver(mSmsReceiver, dataFilter); 601 } 602 603 private static class SmsBroadcastReceiver extends BroadcastReceiver { 604 605 private final String mText; 606 607 private CompletableFuture<Boolean> mFuture = new CompletableFuture<>(); 608 SmsBroadcastReceiver(String text)609 public SmsBroadcastReceiver(String text) { 610 mText = text; 611 } 612 613 @Override onReceive(Context context, Intent intent)614 public void onReceive(Context context, Intent intent) { 615 SmsMessage[] messages = Sms.Intents.getMessagesFromIntent(intent); 616 StringBuilder messageBody = new StringBuilder(); 617 CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder(); 618 for (SmsMessage message : messages) { 619 if (message.getMessageBody() != null) { 620 messageBody.append(message.getMessageBody()); 621 } else if (message.getUserData() != null) { 622 ByteBuffer byteBuffer = ByteBuffer.wrap(message.getUserData()); 623 try { 624 messageBody.append(decoder.decode(byteBuffer).toString()); 625 } catch (CharacterCodingException e) { 626 return; 627 } 628 } 629 } 630 if (!TextUtils.equals(mText, messageBody.toString())) { 631 return; 632 } 633 mFuture.complete(true); 634 } 635 assertReceived(long timeoutMillis)636 public void assertReceived(long timeoutMillis) { 637 try { 638 mFuture.get(timeoutMillis, TimeUnit.MILLISECONDS); 639 } catch (InterruptedException | ExecutionException | TimeoutException e) { 640 throw new RuntimeException(e); 641 } 642 } 643 assertNotReceived(long timeoutMillis)644 public void assertNotReceived(long timeoutMillis) { 645 try { 646 mFuture.get(timeoutMillis, TimeUnit.MILLISECONDS); 647 throw new RuntimeException("Unexpected SMS received"); 648 } catch (TimeoutException e) { 649 // expected 650 } catch (InterruptedException | ExecutionException e) { 651 throw new RuntimeException(e); 652 } 653 } 654 } 655 656 private static class SentSmsObserver extends ContentObserver implements AutoCloseable { 657 658 private final Context mContext; 659 private final String mText; 660 661 public CompletableFuture<Boolean> mFuture = new CompletableFuture<>(); 662 SentSmsObserver(Context context, String text)663 public SentSmsObserver(Context context, String text) { 664 super(new Handler(Looper.getMainLooper())); 665 mContext = context; 666 mText = text; 667 mContext.getContentResolver().registerContentObserver(Sms.CONTENT_URI, true, this); 668 } 669 assertNotChanged()670 public void assertNotChanged() { 671 try { 672 mFuture.get(EVENT_NOT_RECEIVED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 673 fail("Visual voicemail SMS should not be added into the sent SMS"); 674 } catch (TimeoutException e) { 675 // expected 676 } catch (ExecutionException | InterruptedException e) { 677 throw new RuntimeException(e); 678 } 679 680 } 681 682 @Override onChange(boolean selfChange, Uri uri)683 public void onChange(boolean selfChange, Uri uri) { 684 try (Cursor cursor = mContext.getContentResolver() 685 .query(uri, new String[] {Sms.TYPE, Sms.BODY}, null, null, null)) { 686 if (cursor == null){ 687 return; 688 } 689 if (!cursor.moveToFirst()){ 690 return; 691 } 692 if (cursor.getInt(0) == Sms.MESSAGE_TYPE_SENT && TextUtils 693 .equals(cursor.getString(1), mText)) { 694 mFuture.complete(true); 695 } 696 } catch (SQLiteException e) { 697 698 } 699 } 700 701 @Override close()702 public void close() { 703 mContext.getContentResolver().unregisterContentObserver(this); 704 } 705 } 706 hasTelephony(Context context)707 private static boolean hasTelephony(Context context) { 708 final PackageManager packageManager = context.getPackageManager(); 709 return packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY) && 710 packageManager.hasSystemFeature(PackageManager.FEATURE_CONNECTION_SERVICE); 711 } 712 hasDataSms()713 private boolean hasDataSms() { 714 if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) { 715 return false; 716 } 717 String mccmnc = mTelephonyManager.getSimOperator(); 718 return !CarrierCapability.UNSUPPORT_DATA_SMS_MESSAGES.contains(mccmnc); 719 } 720 setDefaultDialer(Instrumentation instrumentation, String packageName)721 private static String setDefaultDialer(Instrumentation instrumentation, String packageName) 722 throws Exception { 723 return executeShellCommand(instrumentation, COMMAND_SET_DEFAULT_DIALER + packageName); 724 } 725 getDefaultDialer(Instrumentation instrumentation)726 private static String getDefaultDialer(Instrumentation instrumentation) throws Exception { 727 return executeShellCommand(instrumentation, COMMAND_GET_DEFAULT_DIALER); 728 } 729 730 /** 731 * Executes the given shell command and returns the output in a string. Note that even if we 732 * don't care about the output, we have to read the stream completely to make the command 733 * execute. 734 */ executeShellCommand(Instrumentation instrumentation, String command)735 private static String executeShellCommand(Instrumentation instrumentation, 736 String command) throws Exception { 737 final ParcelFileDescriptor parcelFileDescriptor = 738 instrumentation.getUiAutomation().executeShellCommand(command); 739 BufferedReader bufferedReader = null; 740 try (InputStream in = new FileInputStream(parcelFileDescriptor.getFileDescriptor())) { 741 bufferedReader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8)); 742 String string = null; 743 StringBuilder out = new StringBuilder(); 744 while ((string = bufferedReader.readLine()) != null) { 745 out.append(string); 746 } 747 return out.toString(); 748 } finally { 749 if (bufferedReader != null) { 750 closeQuietly(bufferedReader); 751 } 752 closeQuietly(parcelFileDescriptor); 753 } 754 } 755 closeQuietly(AutoCloseable closeable)756 private static void closeQuietly(AutoCloseable closeable) { 757 if (closeable != null) { 758 try { 759 closeable.close(); 760 } catch (RuntimeException rethrown) { 761 throw rethrown; 762 } catch (Exception ignored) { 763 } 764 } 765 } 766 } 767