1 /*
2  * Copyright (C) 2015 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.os.cts;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static com.google.common.truth.Truth.assertWithMessage;
21 
22 import static org.junit.Assert.assertTrue;
23 import static org.junit.Assert.fail;
24 
25 import android.content.ComponentName;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.ServiceConnection;
29 import android.content.pm.PackageManager;
30 import android.net.TrafficStats;
31 import android.net.Uri;
32 import android.os.IBinder;
33 import android.os.RemoteException;
34 import android.os.StrictMode;
35 import android.os.StrictMode.ThreadPolicy.Builder;
36 import android.os.StrictMode.ViolationInfo;
37 import android.os.strictmode.CleartextNetworkViolation;
38 import android.os.strictmode.CustomViolation;
39 import android.os.strictmode.DiskReadViolation;
40 import android.os.strictmode.DiskWriteViolation;
41 import android.os.strictmode.ExplicitGcViolation;
42 import android.os.strictmode.FileUriExposedViolation;
43 import android.os.strictmode.InstanceCountViolation;
44 import android.os.strictmode.LeakedClosableViolation;
45 import android.os.strictmode.NetworkViolation;
46 import android.os.strictmode.NonSdkApiUsedViolation;
47 import android.os.strictmode.UntaggedSocketViolation;
48 import android.os.strictmode.Violation;
49 import android.platform.test.annotations.AppModeFull;
50 import android.platform.test.annotations.AppModeInstant;
51 import android.system.Os;
52 import android.system.OsConstants;
53 import android.util.Log;
54 
55 import androidx.test.InstrumentationRegistry;
56 import androidx.test.runner.AndroidJUnit4;
57 
58 import org.junit.After;
59 import org.junit.Before;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 
63 import java.io.File;
64 import java.io.FileDescriptor;
65 import java.io.FileInputStream;
66 import java.io.FileNotFoundException;
67 import java.io.FileOutputStream;
68 import java.io.IOException;
69 import java.net.HttpURLConnection;
70 import java.net.Socket;
71 import java.net.URL;
72 import java.util.ArrayList;
73 import java.util.List;
74 import java.util.concurrent.ArrayBlockingQueue;
75 import java.util.concurrent.BlockingQueue;
76 import java.util.concurrent.CountDownLatch;
77 import java.util.concurrent.ExecutionException;
78 import java.util.concurrent.LinkedBlockingQueue;
79 import java.util.concurrent.TimeUnit;
80 import java.util.function.Consumer;
81 
82 /** Tests for {@link StrictMode} */
83 @RunWith(AndroidJUnit4.class)
84 public class StrictModeTest {
85     private static final String TAG = "StrictModeTest";
86     private static final String REMOTE_SERVICE_ACTION = "android.app.REMOTESERVICE";
87 
88     private StrictMode.ThreadPolicy mThreadPolicy;
89     private StrictMode.VmPolicy mVmPolicy;
90 
getContext()91     private Context getContext() {
92         return InstrumentationRegistry.getContext();
93     }
94 
95     @Before
setUp()96     public void setUp() {
97         mThreadPolicy = StrictMode.getThreadPolicy();
98         mVmPolicy = StrictMode.getVmPolicy();
99     }
100 
101     @After
tearDown()102     public void tearDown() {
103         StrictMode.setThreadPolicy(mThreadPolicy);
104         StrictMode.setVmPolicy(mVmPolicy);
105     }
106 
107     public interface ThrowingRunnable {
run()108         void run() throws Exception;
109     }
110 
111     @Test
testThreadBuilder()112     public void testThreadBuilder() throws Exception {
113         StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyLog().build();
114         StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(policy).build());
115 
116         final File test = File.createTempFile("foo", "bar");
117         inspectViolation(
118                 test::exists,
119                 info -> {
120                     assertThat(info.getViolationDetails()).isNull();
121                     assertThat(info.getStackTrace()).contains("DiskReadViolation");
122                 });
123     }
124 
125     @Test
testUnclosedCloseable()126     public void testUnclosedCloseable() throws Exception {
127         StrictMode.setVmPolicy(
128                 new StrictMode.VmPolicy.Builder().detectLeakedClosableObjects().build());
129 
130         inspectViolation(
131                 () -> leakCloseable("leaked.txt"),
132                 info -> {
133                     assertThat(info.getViolationDetails())
134                             .isEqualTo(
135                                     "A resource was acquired at attached stack trace but never released. See java.io.Closeable for information on avoiding resource leaks.");
136                     assertThat(info.getStackTrace())
137                             .contains("Explicit termination method 'close' not called");
138                     assertThat(info.getStackTrace()).contains("leakCloseable");
139                     assertThat(info.getViolationClass())
140                             .isAssignableTo(LeakedClosableViolation.class);
141                 });
142     }
143 
leakCloseable(String fileName)144     private void leakCloseable(String fileName) throws InterruptedException {
145         final CountDownLatch finalizedSignal = new CountDownLatch(1);
146         try {
147             new FileOutputStream(new File(getContext().getFilesDir(), fileName)) {
148                 @Override
149                 protected void finalize() throws IOException {
150                     super.finalize();
151                     finalizedSignal.countDown();
152                 }
153             };
154         } catch (FileNotFoundException e) {
155             throw new RuntimeException(e);
156         }
157         Runtime.getRuntime().gc();
158         Runtime.getRuntime().runFinalization();
159         // Sometimes it needs extra prodding.
160         if (!finalizedSignal.await(5, TimeUnit.SECONDS)) {
161             Runtime.getRuntime().gc();
162             Runtime.getRuntime().runFinalization();
163         }
164     }
165 
166     @Test
testClassInstanceLimit()167     public void testClassInstanceLimit() throws Exception {
168         StrictMode.setVmPolicy(
169                 new StrictMode.VmPolicy.Builder()
170                         .setClassInstanceLimit(LimitedClass.class, 1)
171                         .build());
172         List<LimitedClass> references = new ArrayList<>();
173         assertNoViolation(() -> references.add(new LimitedClass()));
174         references.add(new LimitedClass());
175         inspectViolation(
176                 StrictMode::conditionallyCheckInstanceCounts,
177                 info -> assertThat(info.getViolationClass())
178                         .isAssignableTo(InstanceCountViolation.class));
179     }
180 
181     private static final class LimitedClass {}
182 
183     /** Insecure connection should be detected */
184     @AppModeFull
185     @Test
testCleartextNetwork()186     public void testCleartextNetwork() throws Exception {
187         if (!hasInternetConnection()) {
188             Log.i(TAG, "testCleartextNetwork() ignored on device without Internet");
189             return;
190         }
191 
192         StrictMode.setVmPolicy(
193                 new StrictMode.VmPolicy.Builder().detectCleartextNetwork().penaltyLog().build());
194 
195         inspectViolation(
196                 () ->
197                         ((HttpURLConnection) new URL("http://example.com/").openConnection())
198                                 .getResponseCode(),
199                 info -> assertThat(info.getViolationClass())
200                         .isAssignableTo(CleartextNetworkViolation.class));
201     }
202 
203     /** Secure connection should be ignored */
204     @Test
testEncryptedNetwork()205     public void testEncryptedNetwork() throws Exception {
206         if (!hasInternetConnection()) {
207             Log.i(TAG, "testEncryptedNetwork() ignored on device without Internet");
208             return;
209         }
210 
211         StrictMode.setVmPolicy(
212                 new StrictMode.VmPolicy.Builder().detectCleartextNetwork().penaltyLog().build());
213 
214         assertNoViolation(
215                 () ->
216                         ((HttpURLConnection) new URL("https://example.com/").openConnection())
217                                 .getResponseCode());
218     }
219 
220     @Test
testFileUriExposure()221     public void testFileUriExposure() throws Exception {
222         StrictMode.setVmPolicy(
223                 new StrictMode.VmPolicy.Builder().detectFileUriExposure().penaltyLog().build());
224 
225         final Uri badUri = Uri.fromFile(new File("/sdcard/meow.jpg"));
226         inspectViolation(
227                 () -> {
228                     Intent intent = new Intent(Intent.ACTION_VIEW);
229                     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
230                     intent.setDataAndType(badUri, "image/jpeg");
231                     getContext().startActivity(intent);
232                 },
233                 info -> {
234                     assertThat(info.getStackTrace()).contains(badUri + " exposed beyond app");
235                 });
236 
237         final Uri goodUri = Uri.parse("content://com.example/foobar");
238         assertNoViolation(
239                 () -> {
240                     Intent intent = new Intent(Intent.ACTION_VIEW);
241                     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
242                     intent.setDataAndType(goodUri, "image/jpeg");
243                     getContext().startActivity(intent);
244                 });
245     }
246 
247     @Test
testFileUriExposure_Chooser()248     public void testFileUriExposure_Chooser() throws Exception {
249         StrictMode.setVmPolicy(
250                 new StrictMode.VmPolicy.Builder().detectFileUriExposure().penaltyLog().build());
251 
252         final Uri badUri = Uri.fromFile(new File("/sdcard/meow.jpg"));
253         inspectViolation(
254                 () -> {
255                     Intent intent = new Intent(Intent.ACTION_SEND);
256                     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
257                     intent.setType("image/jpeg");
258                     intent.putExtra(Intent.EXTRA_STREAM, badUri);
259 
260                     Intent chooser = Intent.createChooser(intent, "CTS");
261                     chooser.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
262                     getContext().startActivity(chooser);
263                 },
264                 info -> {
265                     assertThat(info.getStackTrace()).contains(badUri + " exposed beyond app");
266                 });
267     }
268 
269     @Test
testContentUriWithoutPermission()270     public void testContentUriWithoutPermission() throws Exception {
271         StrictMode.setVmPolicy(
272                 new StrictMode.VmPolicy.Builder()
273                         .detectContentUriWithoutPermission()
274                         .penaltyLog()
275                         .build());
276 
277         final Uri uri = Uri.parse("content://com.example/foobar");
278         inspectViolation(
279                 () -> {
280                     Intent intent = new Intent(Intent.ACTION_VIEW);
281                     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
282                     intent.setDataAndType(uri, "image/jpeg");
283                     getContext().startActivity(intent);
284                 },
285                 info ->
286                         assertThat(info.getStackTrace())
287                                 .contains(uri + " exposed beyond app"));
288 
289         assertNoViolation(
290                 () -> {
291                     Intent intent = new Intent(Intent.ACTION_VIEW);
292                     intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
293                     intent.setDataAndType(uri, "image/jpeg");
294                     intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
295                     getContext().startActivity(intent);
296                 });
297     }
298 
299     @AppModeFull
300     @Test
testUntaggedSocketsHttp()301     public void testUntaggedSocketsHttp() throws Exception {
302         if (!hasInternetConnection()) {
303             Log.i(TAG, "testUntaggedSockets() ignored on device without Internet");
304             return;
305         }
306 
307         StrictMode.setVmPolicy(
308                 new StrictMode.VmPolicy.Builder().detectUntaggedSockets().penaltyLog().build());
309 
310         inspectViolation(
311                 () ->
312                         ((HttpURLConnection) new URL("http://example.com/").openConnection())
313                                 .getResponseCode(),
314                 info -> assertThat(info.getViolationClass())
315                         .isAssignableTo(UntaggedSocketViolation.class));
316 
317         assertNoViolation(
318                 () -> {
319                     TrafficStats.setThreadStatsTag(0xDECAFBAD);
320                     try {
321                         ((HttpURLConnection) new URL("http://example.com/").openConnection())
322                                 .getResponseCode();
323                     } finally {
324                         TrafficStats.clearThreadStatsTag();
325                     }
326                 });
327     }
328 
329     @Test
testUntaggedSocketsRaw()330     public void testUntaggedSocketsRaw() throws Exception {
331         if (!hasInternetConnection()) {
332             Log.i(TAG, "testUntaggedSockets() ignored on device without Internet");
333             return;
334         }
335 
336         StrictMode.setVmPolicy(
337                 new StrictMode.VmPolicy.Builder().detectUntaggedSockets().penaltyLog().build());
338 
339         assertNoViolation(
340                 () -> {
341                     TrafficStats.setThreadStatsTag(0xDECAFBAD);
342                     try (Socket socket = new Socket("example.com", 80)) {
343                         socket.getOutputStream().close();
344                     } finally {
345                         TrafficStats.clearThreadStatsTag();
346                     }
347                 });
348 
349         inspectViolation(
350                 () -> {
351                     try (Socket socket = new Socket("example.com", 80)) {
352                         socket.getOutputStream().close();
353                     }
354                 },
355                 info -> assertThat(info.getViolationClass())
356                         .isAssignableTo(UntaggedSocketViolation.class));
357     }
358 
359     private static final int PERMISSION_USER_ONLY = 0600;
360 
361     @Test
testRead()362     public void testRead() throws Exception {
363         final File test = File.createTempFile("foo", "bar");
364         final File dir = test.getParentFile();
365 
366         final FileInputStream is = new FileInputStream(test);
367         final FileDescriptor fd =
368                 Os.open(test.getAbsolutePath(), OsConstants.O_RDONLY, PERMISSION_USER_ONLY);
369 
370         StrictMode.setThreadPolicy(
371                 new StrictMode.ThreadPolicy.Builder().detectDiskReads().penaltyLog().build());
372         inspectViolation(
373                 test::exists,
374                 info -> {
375                     assertThat(info.getViolationDetails()).isNull();
376                     assertThat(info.getStackTrace()).contains("DiskReadViolation");
377                 });
378 
379         Consumer<ViolationInfo> assertDiskReadPolicy = info -> assertThat(
380                 info.getViolationClass()).isAssignableTo(DiskReadViolation.class);
381         inspectViolation(test::exists, assertDiskReadPolicy);
382         inspectViolation(test::length, assertDiskReadPolicy);
383         inspectViolation(dir::list, assertDiskReadPolicy);
384         inspectViolation(is::read, assertDiskReadPolicy);
385 
386         inspectViolation(() -> new FileInputStream(test), assertDiskReadPolicy);
387         inspectViolation(
388                 () -> Os.open(test.getAbsolutePath(), OsConstants.O_RDONLY, PERMISSION_USER_ONLY),
389                 assertDiskReadPolicy);
390         inspectViolation(() -> Os.read(fd, new byte[10], 0, 1), assertDiskReadPolicy);
391     }
392 
393     @Test
testWrite()394     public void testWrite() throws Exception {
395         File file = File.createTempFile("foo", "bar");
396 
397         final FileOutputStream os = new FileOutputStream(file);
398         final FileDescriptor fd =
399                 Os.open(file.getAbsolutePath(), OsConstants.O_RDWR, PERMISSION_USER_ONLY);
400 
401         StrictMode.setThreadPolicy(
402                 new StrictMode.ThreadPolicy.Builder().detectDiskWrites().penaltyLog().build());
403 
404         inspectViolation(
405                 file::createNewFile,
406                 info -> {
407                     assertThat(info.getViolationDetails()).isNull();
408                     assertThat(info.getStackTrace()).contains("DiskWriteViolation");
409                 });
410 
411         Consumer<ViolationInfo> assertDiskWritePolicy = info -> assertThat(
412                 info.getViolationClass()).isAssignableTo(DiskWriteViolation.class);
413 
414         inspectViolation(() -> File.createTempFile("foo", "bar"), assertDiskWritePolicy);
415         inspectViolation(() -> new FileOutputStream(file), assertDiskWritePolicy);
416         inspectViolation(file::delete, assertDiskWritePolicy);
417         inspectViolation(file::createNewFile, assertDiskWritePolicy);
418         inspectViolation(() -> os.write(32), assertDiskWritePolicy);
419 
420         inspectViolation(
421                 () -> Os.open(file.getAbsolutePath(), OsConstants.O_RDWR, PERMISSION_USER_ONLY),
422                 assertDiskWritePolicy);
423         inspectViolation(() -> Os.write(fd, new byte[10], 0, 1), assertDiskWritePolicy);
424         inspectViolation(() -> Os.fsync(fd), assertDiskWritePolicy);
425         inspectViolation(
426                 () -> file.renameTo(new File(file.getParent(), "foobar")), assertDiskWritePolicy);
427     }
428 
429     @AppModeFull
430     @Test
testNetwork()431     public void testNetwork() throws Exception {
432         if (!hasInternetConnection()) {
433             Log.i(TAG, "testUntaggedSockets() ignored on device without Internet");
434             return;
435         }
436 
437         StrictMode.setThreadPolicy(
438                 new StrictMode.ThreadPolicy.Builder().detectNetwork().penaltyLog().build());
439 
440         inspectViolation(
441                 () -> {
442                     try (Socket socket = new Socket("example.com", 80)) {
443                         socket.getOutputStream().close();
444                     }
445                 },
446                 info -> assertThat(info.getViolationClass())
447                         .isAssignableTo(NetworkViolation.class));
448         inspectViolation(
449                 () ->
450                         ((HttpURLConnection) new URL("http://example.com/").openConnection())
451                                 .getResponseCode(),
452                 info -> assertThat(info.getViolationClass())
453                         .isAssignableTo(NetworkViolation.class));
454     }
455 
456     @Test
testExplicitGc()457     public void testExplicitGc() throws Exception {
458         StrictMode.setThreadPolicy(
459                 new StrictMode.ThreadPolicy.Builder().detectExplicitGc().penaltyLog().build());
460 
461         inspectViolation(
462                 () -> { Runtime.getRuntime().gc(); },
463                 info -> assertThat(info.getViolationClass())
464                         .isAssignableTo(ExplicitGcViolation.class));
465     }
466 
467     @Test
testViolationAcrossBinder()468     public void testViolationAcrossBinder() throws Exception {
469         runWithRemoteServiceBound(
470                 getContext(),
471                 service -> {
472                     StrictMode.setThreadPolicy(
473                             new Builder().detectDiskWrites().penaltyLog().build());
474 
475                     try {
476                         inspectViolation(
477                                 () -> service.performDiskWrite(),
478                                 (info) -> {
479                                     assertThat(info.getViolationClass())
480                                             .isAssignableTo(DiskWriteViolation.class);
481                                     assertThat(info.getViolationDetails())
482                                             .isNull(); // Disk write has no message.
483                                     assertThat(info.getStackTrace())
484                                             .contains("DiskWriteViolation");
485                                     assertThat(info.getStackTrace())
486                                             .contains(
487                                                     "at android.os.StrictMode$AndroidBlockGuardPolicy.onWriteToDisk");
488                                     assertThat(info.getStackTrace())
489                                             .contains("# via Binder call with stack:");
490                                     assertThat(info.getStackTrace())
491                                             .contains(
492                                                     "at android.os.cts.ISecondary$Stub$Proxy.performDiskWrite");
493                                 });
494                         assertNoViolation(() -> service.getPid());
495                     } catch (Exception e) {
496                         throw new RuntimeException(e);
497                     }
498                 });
499     }
500 
checkNonSdkApiUsageViolation(boolean blacklist, String className, String methodName, Class<?>... paramTypes)501     private void checkNonSdkApiUsageViolation(boolean blacklist, String className,
502             String methodName, Class<?>... paramTypes) throws Exception {
503         Class<?> clazz = Class.forName(className);
504         inspectViolation(
505             () -> {
506                 try {
507                     java.lang.reflect.Method m = clazz.getDeclaredMethod(methodName, paramTypes);
508                     if (blacklist) {
509                         fail();
510                     }
511                 } catch (NoSuchMethodException expected) {
512                   if (!blacklist) {
513                     fail();
514                   }
515                 }
516             },
517             info -> {
518                 assertThat(info).isNotNull();
519                 assertThat(info.getViolationClass())
520                         .isAssignableTo(NonSdkApiUsedViolation.class);
521                 assertThat(info.getViolationDetails()).contains(methodName);
522                 assertThat(info.getStackTrace()).contains("checkNonSdkApiUsageViolation");
523             }
524         );
525     }
526 
527     @Test
testNonSdkApiUsage()528     public void testNonSdkApiUsage() throws Exception {
529         StrictMode.VmPolicy oldVmPolicy = StrictMode.getVmPolicy();
530         StrictMode.ThreadPolicy oldThreadPolicy = StrictMode.getThreadPolicy();
531         try {
532             StrictMode.setVmPolicy(
533                     new StrictMode.VmPolicy.Builder().detectNonSdkApiUsage().build());
534             checkNonSdkApiUsageViolation(
535                 true, "dalvik.system.VMRuntime", "setHiddenApiExemptions", String[].class);
536             // verify that mutliple uses of a light greylist API are detected.
537             checkNonSdkApiUsageViolation(false, "dalvik.system.VMRuntime", "getRuntime");
538             checkNonSdkApiUsageViolation(false, "dalvik.system.VMRuntime", "getRuntime");
539 
540             // Verify that the VM policy is turned off after a call to permitNonSdkApiUsage.
541             StrictMode.setVmPolicy(
542                 new StrictMode.VmPolicy.Builder().permitNonSdkApiUsage().build());
543             assertNoViolation(() -> {
544                   Class<?> clazz = Class.forName("dalvik.system.VMRuntime");
545                   try {
546                       clazz.getDeclaredMethod("getRuntime");
547                   } catch (NoSuchMethodException maybe) {
548                   }
549             });
550         } finally {
551             StrictMode.setVmPolicy(oldVmPolicy);
552             StrictMode.setThreadPolicy(oldThreadPolicy);
553         }
554     }
555 
556     @Test
testThreadPenaltyListener()557     public void testThreadPenaltyListener() throws Exception {
558         final BlockingQueue<Violation> violations = new ArrayBlockingQueue<>(1);
559         StrictMode.setThreadPolicy(
560                 new StrictMode.ThreadPolicy.Builder().detectCustomSlowCalls()
561                         .penaltyListener(getContext().getMainExecutor(), (v) -> {
562                             violations.add(v);
563                         }).build());
564 
565         StrictMode.noteSlowCall("foo");
566 
567         final Violation v = violations.poll(5, TimeUnit.SECONDS);
568         assertTrue(v instanceof CustomViolation);
569     }
570 
571     @Test
testVmPenaltyListener()572     public void testVmPenaltyListener() throws Exception {
573         final BlockingQueue<Violation> violations = new ArrayBlockingQueue<>(1);
574         StrictMode.setVmPolicy(
575                 new StrictMode.VmPolicy.Builder().detectFileUriExposure()
576                         .penaltyListener(getContext().getMainExecutor(), (v) -> {
577                             violations.add(v);
578                         }).build());
579 
580         Intent intent = new Intent(Intent.ACTION_VIEW);
581         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
582         intent.setDataAndType(Uri.fromFile(new File("/sdcard/meow.jpg")), "image/jpeg");
583         getContext().startActivity(intent);
584 
585         final Violation v = violations.poll(5, TimeUnit.SECONDS);
586         assertTrue(v instanceof FileUriExposedViolation);
587     }
588 
589     @AppModeInstant
590     @Test
testNoCleartextHttpTrafficAllowed()591     public void testNoCleartextHttpTrafficAllowed() throws Exception {
592         if (!hasInternetConnection()) {
593             Log.i(TAG, "testNoCleartextHttpTrafficAllowed() ignored on device without Internet");
594             return;
595         }
596 
597         StrictMode.setVmPolicy(
598                 new StrictMode.VmPolicy.Builder().detectCleartextNetwork().penaltyLog().build());
599 
600         try {
601             inspectViolation(
602                     () ->
603                             ((HttpURLConnection) new URL("http://example.com/").openConnection())
604                                     .getResponseCode(),
605                     info -> assertThat(info.getViolationClass())
606                             .isAssignableTo(CleartextNetworkViolation.class));
607             fail("Instant app was able to send cleartext http traffic.");
608         } catch (IOException ex) {
609             // Expected
610         }
611     }
612 
runWithRemoteServiceBound(Context context, Consumer<ISecondary> consumer)613     private static void runWithRemoteServiceBound(Context context, Consumer<ISecondary> consumer)
614             throws ExecutionException, InterruptedException, RemoteException {
615         BlockingQueue<IBinder> binderHolder = new ArrayBlockingQueue<>(1);
616         ServiceConnection secondaryConnection =
617                 new ServiceConnection() {
618                     public void onServiceConnected(ComponentName className, IBinder service) {
619                         binderHolder.add(service);
620                     }
621 
622                     public void onServiceDisconnected(ComponentName className) {
623                         binderHolder.drainTo(new ArrayList<>());
624                     }
625                 };
626         Intent intent = new Intent(REMOTE_SERVICE_ACTION);
627         intent.setPackage(context.getPackageName());
628 
629         Intent secondaryIntent = new Intent(ISecondary.class.getName());
630         secondaryIntent.setPackage(context.getPackageName());
631         assertThat(
632                         context.bindService(
633                                 secondaryIntent, secondaryConnection, Context.BIND_AUTO_CREATE))
634                 .isTrue();
635         IBinder binder = binderHolder.take();
636         assertThat(binder.queryLocalInterface(binder.getInterfaceDescriptor())).isNull();
637         consumer.accept(ISecondary.Stub.asInterface(binder));
638         context.unbindService(secondaryConnection);
639         context.stopService(intent);
640     }
641 
assertViolation(String expected, ThrowingRunnable r)642     private static void assertViolation(String expected, ThrowingRunnable r) throws Exception {
643         inspectViolation(r, info -> assertThat(info.getStackTrace()).contains(expected));
644     }
645 
assertNoViolation(ThrowingRunnable r)646     private static void assertNoViolation(ThrowingRunnable r) throws Exception {
647         inspectViolation(
648                 r, info -> assertWithMessage("Unexpected violation").that(info).isNull());
649     }
650 
inspectViolation( ThrowingRunnable violating, Consumer<ViolationInfo> consume)651     private static void inspectViolation(
652             ThrowingRunnable violating, Consumer<ViolationInfo> consume) throws Exception {
653         final LinkedBlockingQueue<ViolationInfo> violations = new LinkedBlockingQueue<>();
654         StrictMode.setViolationLogger(violations::add);
655 
656         try {
657             violating.run();
658             consume.accept(violations.poll(5, TimeUnit.SECONDS));
659         } finally {
660             StrictMode.setViolationLogger(null);
661         }
662     }
663 
hasInternetConnection()664     private boolean hasInternetConnection() {
665         final PackageManager pm = getContext().getPackageManager();
666         return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
667                 || pm.hasSystemFeature(PackageManager.FEATURE_WIFI)
668                 || pm.hasSystemFeature(PackageManager.FEATURE_ETHERNET);
669     }
670 }
671