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