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