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