1 // Copyright 2020 The Chromium Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.base.test.util; 6 7 import androidx.test.InstrumentationRegistry; 8 9 import org.chromium.base.StrictModeContext; 10 11 import java.io.Closeable; 12 import java.io.IOException; 13 import java.util.concurrent.Callable; 14 15 /** 16 * A Closeable that manages another Closeable running on android's main thread. 17 * 18 * The Closeable is both created and closed on the main thread. 19 * Note that both operations are synchronous. 20 */ 21 public final class CloseableOnMainThread implements Closeable { 22 private Closeable mCloseable; 23 private Exception mException; 24 25 /** 26 * Execute a closeable callable on the main thread, blocking until it is complete. 27 * 28 * @param initializer A closeable callable to be executed on the main thread 29 * @throws Exception Thrown if the initializer throws Exception 30 */ CloseableOnMainThread(Callable<Closeable> initializer)31 public CloseableOnMainThread(Callable<Closeable> initializer) throws Exception { 32 InstrumentationRegistry.getInstrumentation() 33 .runOnMainSync( 34 () -> { 35 try { 36 mCloseable = initializer.call(); 37 } catch (Exception e) { 38 mException = e; 39 } 40 }); 41 if (mException != null) { 42 throw new Exception(mException.getCause()); 43 } 44 } 45 46 /** 47 * Close the closeable on the main thread, blocking until it is complete. 48 * 49 * @throws IOException Thrown if the closeable throws IOException 50 */ 51 @Override close()52 public void close() throws IOException { 53 InstrumentationRegistry.getInstrumentation() 54 .runOnMainSync( 55 () -> { 56 try { 57 mCloseable.close(); 58 } catch (IOException e) { 59 mException = e; 60 } 61 }); 62 if (mException != null) { 63 throw new IOException(mException.getCause()); 64 } 65 } 66 67 /** 68 * Enables try-with-resources compatible StrictMode violation allowlisting on android's main 69 * thread. 70 * 71 * Prefer "ignored" as the variable name to appease Android Studio's "Unused symbol" inspection. 72 * 73 * Example: 74 * <pre> 75 * try (CloseableOnMainThread ignored = 76 * CloseableOnMainThread.StrictMode.allowDiskWrites()) { 77 * return Example.doThingThatRequiresDiskWrites(); 78 * } 79 * </pre> 80 * 81 */ 82 public static class StrictMode { StrictMode()83 private StrictMode() {} 84 85 /** 86 * Convenience method for disabling all thread-level StrictMode checks with 87 * try-with-resources. Includes everything listed here: 88 * https://developer.android.com/reference/android/os/StrictMode.ThreadPolicy.Builder.html 89 */ allowAllThreadPolicies()90 public static CloseableOnMainThread allowAllThreadPolicies() throws Exception { 91 return new CloseableOnMainThread( 92 () -> { 93 return StrictModeContext.allowAllThreadPolicies(); 94 }); 95 } 96 97 /** Convenience method for disabling StrictMode for disk-writes with try-with-resources. */ allowDiskWrites()98 public static CloseableOnMainThread allowDiskWrites() throws Exception { 99 return new CloseableOnMainThread( 100 () -> { 101 return StrictModeContext.allowDiskWrites(); 102 }); 103 } 104 } 105 } 106