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