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