1 /* 2 * Copyright (C) 2014 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 com.android.compatibility.common.util; 18 19 import static org.junit.Assert.assertTrue; 20 import static org.junit.Assert.fail; 21 22 import android.app.ActivityManager; 23 import android.app.ActivityManager.MemoryInfo; 24 import android.app.Instrumentation; 25 import android.app.UiAutomation; 26 import android.content.Context; 27 import android.os.ParcelFileDescriptor; 28 import android.os.StatFs; 29 import android.util.Log; 30 31 import androidx.annotation.NonNull; 32 import androidx.test.InstrumentationRegistry; 33 34 import java.io.ByteArrayOutputStream; 35 import java.io.FileInputStream; 36 import java.io.IOException; 37 import java.util.concurrent.Callable; 38 import java.util.concurrent.atomic.AtomicReference; 39 import java.util.function.Predicate; 40 41 public class SystemUtil { 42 private static final String TAG = "CtsSystemUtil"; 43 private static final long TIMEOUT_MILLIS = 10000; 44 getFreeDiskSize(Context context)45 public static long getFreeDiskSize(Context context) { 46 final StatFs statFs = new StatFs(context.getFilesDir().getAbsolutePath()); 47 return (long)statFs.getAvailableBlocks() * statFs.getBlockSize(); 48 } 49 getFreeMemory(Context context)50 public static long getFreeMemory(Context context) { 51 final MemoryInfo info = new MemoryInfo(); 52 ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(info); 53 return info.availMem; 54 } 55 getTotalMemory(Context context)56 public static long getTotalMemory(Context context) { 57 final MemoryInfo info = new MemoryInfo(); 58 ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(info); 59 return info.totalMem; 60 } 61 62 /** 63 * Executes a shell command using shell user identity, and return the standard output in string 64 * <p>Note: calling this function requires API level 21 or above 65 * @param instrumentation {@link Instrumentation} instance, obtained from a test running in 66 * instrumentation framework 67 * @param cmd the command to run 68 * @return the standard output of the command 69 * @throws Exception 70 */ runShellCommand(Instrumentation instrumentation, String cmd)71 public static String runShellCommand(Instrumentation instrumentation, String cmd) 72 throws IOException { 73 return runShellCommand(instrumentation.getUiAutomation(), cmd); 74 } 75 76 /** 77 * Executes a shell command using shell user identity, and return the standard output in string 78 * <p>Note: calling this function requires API level 21 or above 79 * @param automation {@link UiAutomation} instance, obtained from a test running in 80 * instrumentation framework 81 * @param cmd the command to run 82 * @return the standard output of the command 83 * @throws Exception 84 */ runShellCommand(UiAutomation automation, String cmd)85 public static String runShellCommand(UiAutomation automation, String cmd) 86 throws IOException { 87 return new String(runShellCommandByteOutput(automation, cmd)); 88 } 89 90 /** 91 * Executes a shell command using shell user identity, and return the standard output as a byte 92 * array 93 * <p>Note: calling this function requires API level 21 or above 94 * 95 * @param automation {@link UiAutomation} instance, obtained from a test running in 96 * instrumentation framework 97 * @param cmd the command to run 98 * @return the standard output of the command as a byte array 99 */ runShellCommandByteOutput(UiAutomation automation, String cmd)100 static byte[] runShellCommandByteOutput(UiAutomation automation, String cmd) 101 throws IOException { 102 Log.v(TAG, "Running command: " + cmd); 103 if (cmd.startsWith("pm grant ") || cmd.startsWith("pm revoke ")) { 104 throw new UnsupportedOperationException("Use UiAutomation.grantRuntimePermission() " 105 + "or revokeRuntimePermission() directly, which are more robust."); 106 } 107 ParcelFileDescriptor pfd = automation.executeShellCommand(cmd); 108 try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { 109 return FileUtils.readInputStreamFully(fis); 110 } 111 } 112 113 /** 114 * Simpler version of {@link #runShellCommand(Instrumentation, String)}. 115 */ runShellCommand(String cmd)116 public static String runShellCommand(String cmd) { 117 try { 118 return runShellCommand(InstrumentationRegistry.getInstrumentation(), cmd); 119 } catch (IOException e) { 120 fail("Failed reading command output: " + e); 121 return ""; 122 } 123 } 124 125 /** 126 * Same as {@link #runShellCommand(String)}, with optionally 127 * check the result using {@code resultChecker}. 128 */ runShellCommand(String cmd, Predicate<String> resultChecker)129 public static String runShellCommand(String cmd, Predicate<String> resultChecker) { 130 final String result = runShellCommand(cmd); 131 if (resultChecker != null) { 132 assertTrue("Assertion failed. Command was: " + cmd + "\n" 133 + "Output was:\n" + result, 134 resultChecker.test(result)); 135 } 136 return result; 137 } 138 139 /** 140 * Same as {@link #runShellCommand(String)}, but fails if the output is not empty. 141 */ runShellCommandForNoOutput(String cmd)142 public static String runShellCommandForNoOutput(String cmd) { 143 final String result = runShellCommand(cmd); 144 assertTrue("Command failed. Command was: " + cmd + "\n" 145 + "Didn't expect any output, but the output was:\n" + result, 146 result.length() == 0); 147 return result; 148 } 149 150 /** 151 * Runs a command and print the result on logcat. 152 */ runCommandAndPrintOnLogcat(String logtag, String cmd)153 public static void runCommandAndPrintOnLogcat(String logtag, String cmd) { 154 Log.i(logtag, "Executing: " + cmd); 155 final String output = runShellCommand(cmd); 156 for (String line : output.split("\\n", -1)) { 157 Log.i(logtag, line); 158 } 159 } 160 161 /** 162 * Runs a command and return the section matching the patterns. 163 * 164 * @see TextUtils#extractSection 165 */ runCommandAndExtractSection(String cmd, String extractionStartRegex, boolean startInclusive, String extractionEndRegex, boolean endInclusive)166 public static String runCommandAndExtractSection(String cmd, 167 String extractionStartRegex, boolean startInclusive, 168 String extractionEndRegex, boolean endInclusive) { 169 return TextUtils.extractSection(runShellCommand(cmd), extractionStartRegex, startInclusive, 170 extractionEndRegex, endInclusive); 171 } 172 173 /** 174 * Runs a {@link ThrowingSupplier} adopting Shell's permissions, and returning the result. 175 */ runWithShellPermissionIdentity(@onNull ThrowingSupplier<T> supplier)176 public static <T> T runWithShellPermissionIdentity(@NonNull ThrowingSupplier<T> supplier) { 177 final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 178 AtomicReference<T> result = new AtomicReference<>(); 179 runWithShellPermissionIdentity(automan, () -> result.set(supplier.get())); 180 return result.get(); 181 } 182 183 /** 184 * Runs a {@link ThrowingSupplier} adopting a subset of Shell's permissions, 185 * and returning the result. 186 */ runWithShellPermissionIdentity(@onNull ThrowingSupplier<T> supplier, String... permissions)187 public static <T> T runWithShellPermissionIdentity(@NonNull ThrowingSupplier<T> supplier, 188 String... permissions) { 189 final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 190 AtomicReference<T> result = new AtomicReference<>(); 191 runWithShellPermissionIdentity(automan, () -> result.set(supplier.get()), permissions); 192 return result.get(); 193 } 194 195 /** 196 * Runs a {@link ThrowingRunnable} adopting Shell's permissions. 197 */ runWithShellPermissionIdentity(@onNull ThrowingRunnable runnable)198 public static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable) { 199 final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 200 runWithShellPermissionIdentity(automan, runnable); 201 } 202 203 /** 204 * Runs a {@link ThrowingRunnable} adopting a subset of Shell's permissions. 205 */ runWithShellPermissionIdentity(@onNull ThrowingRunnable runnable, String... permissions)206 public static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable, 207 String... permissions) { 208 final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 209 runWithShellPermissionIdentity(automan, runnable, permissions); 210 } 211 212 /** 213 * Runs a {@link ThrowingRunnable} adopting Shell's permissions, where you can specify the 214 * uiAutomation used. 215 */ runWithShellPermissionIdentity( @onNull UiAutomation automan, @NonNull ThrowingRunnable runnable)216 public static void runWithShellPermissionIdentity( 217 @NonNull UiAutomation automan, @NonNull ThrowingRunnable runnable) { 218 runWithShellPermissionIdentity(automan, runnable, null /* permissions */); 219 } 220 221 /** 222 * Runs a {@link ThrowingRunnable} adopting Shell's permissions, where you can specify the 223 * uiAutomation used. 224 * @param automan UIAutomation to use. 225 * @param runnable The code to run with Shell's identity. 226 * @param permissions A subset of Shell's permissions. Passing {@code null} will use all 227 * available permissions. 228 */ runWithShellPermissionIdentity(@onNull UiAutomation automan, @NonNull ThrowingRunnable runnable, String... permissions)229 public static void runWithShellPermissionIdentity(@NonNull UiAutomation automan, 230 @NonNull ThrowingRunnable runnable, String... permissions) { 231 automan.adoptShellPermissionIdentity(permissions); 232 try { 233 runnable.run(); 234 } catch (Exception e) { 235 throw new RuntimeException("Caught exception", e); 236 } finally { 237 automan.dropShellPermissionIdentity(); 238 } 239 } 240 241 /** 242 * Calls a {@link Callable} adopting Shell's permissions. 243 */ callWithShellPermissionIdentity(@onNull Callable<T> callable)244 public static <T> T callWithShellPermissionIdentity(@NonNull Callable<T> callable) 245 throws Exception { 246 final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 247 automan.adoptShellPermissionIdentity(); 248 try { 249 return callable.call(); 250 } finally { 251 automan.dropShellPermissionIdentity(); 252 } 253 } 254 255 /** 256 * Calls a {@link Callable} adopting Shell's permissions. 257 * 258 * @param callable The code to call with Shell's identity. 259 * @param permissions A subset of Shell's permissions. Passing {@code null} will use all 260 * available permissions. */ callWithShellPermissionIdentity(@onNull Callable<T> callable, String... permissions)261 public static <T> T callWithShellPermissionIdentity(@NonNull Callable<T> callable, 262 String... permissions) throws Exception { 263 final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 264 automan.adoptShellPermissionIdentity(permissions); 265 try { 266 return callable.call(); 267 } finally { 268 automan.dropShellPermissionIdentity(); 269 } 270 } 271 272 /** 273 * Make sure that a {@link Runnable} eventually finishes without throwing a {@link 274 * Exception}. 275 * 276 * @param r The {@link Runnable} to run. 277 */ eventually(@onNull ThrowingRunnable r)278 public static void eventually(@NonNull ThrowingRunnable r) { 279 eventually(r, TIMEOUT_MILLIS); 280 } 281 282 /** 283 * Make sure that a {@link Runnable} eventually finishes without throwing a {@link 284 * Exception}. 285 * 286 * @param r The {@link Runnable} to run. 287 * @param r The number of milliseconds to wait for r to not throw 288 */ eventually(@onNull ThrowingRunnable r, long timeoutMillis)289 public static void eventually(@NonNull ThrowingRunnable r, long timeoutMillis) { 290 long start = System.currentTimeMillis(); 291 292 while (true) { 293 try { 294 r.run(); 295 return; 296 } catch (Throwable e) { 297 if (System.currentTimeMillis() - start < timeoutMillis) { 298 try { 299 Thread.sleep(100); 300 } catch (InterruptedException ignored) { 301 throw new RuntimeException(e); 302 } 303 } else { 304 throw new RuntimeException(e); 305 } 306 } 307 } 308 } 309 310 /** 311 * Make sure that a {@link Callable} eventually finishes without throwing a {@link 312 * Exception}. 313 * 314 * @param c The {@link Callable} to run. 315 * 316 * @return The return value of {@code c} 317 */ getEventually(@onNull Callable<T> c)318 public static <T> T getEventually(@NonNull Callable<T> c) throws Exception { 319 return getEventually(c, TIMEOUT_MILLIS); 320 } 321 322 /** 323 * Make sure that a {@link Callable} eventually finishes without throwing a {@link 324 * Exception}. 325 * 326 * @param c The {@link Callable} to run. 327 * @param timeoutMillis The number of milliseconds to wait for r to not throw 328 * 329 * @return The return value of {@code c} 330 */ getEventually(@onNull Callable<T> c, long timeoutMillis)331 public static <T> T getEventually(@NonNull Callable<T> c, long timeoutMillis) throws Exception { 332 long start = System.currentTimeMillis(); 333 334 while (true) { 335 try { 336 return c.call(); 337 } catch (Throwable e) { 338 if (System.currentTimeMillis() - start < timeoutMillis) { 339 try { 340 Thread.sleep(100); 341 } catch (InterruptedException ignored) { 342 throw new RuntimeException(e); 343 } 344 } else { 345 throw e; 346 } 347 } 348 } 349 } 350 } 351