/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.compatibility.common.util; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import android.app.ActivityManager; import android.app.ActivityManager.MemoryInfo; import android.app.Instrumentation; import android.app.UiAutomation; import android.content.Context; import android.os.ParcelFileDescriptor; import android.os.StatFs; import android.util.Log; import androidx.annotation.NonNull; import androidx.test.InstrumentationRegistry; import com.android.modules.utils.build.SdkLevel; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; import java.util.concurrent.Callable; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Predicate; public class SystemUtil { private static final String TAG = "CtsSystemUtil"; private static final long TIMEOUT_MILLIS = 10000; public static long getFreeDiskSize(Context context) { final StatFs statFs = new StatFs(context.getFilesDir().getAbsolutePath()); return (long)statFs.getAvailableBlocks() * statFs.getBlockSize(); } public static long getFreeMemory(Context context) { final MemoryInfo info = new MemoryInfo(); ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(info); return info.availMem; } public static long getTotalMemory(Context context) { final MemoryInfo info = new MemoryInfo(); ((ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE)).getMemoryInfo(info); return info.totalMem; } /** * Executes a shell command using shell user identity, and return the standard output in string *

Note: calling this function requires API level 21 or above * @param instrumentation {@link Instrumentation} instance, obtained from a test running in * instrumentation framework * @param cmd the command to run * @return the standard output of the command * @throws Exception */ public static String runShellCommand(Instrumentation instrumentation, String cmd) throws IOException { return runShellCommand(instrumentation.getUiAutomation(), cmd); } /** * Executes a shell command using shell user identity, and return the standard output in string *

Note: calling this function requires API level 21 or above * @param automation {@link UiAutomation} instance, obtained from a test running in * instrumentation framework * @param cmd the command to run * @return the standard output of the command * @throws Exception */ public static String runShellCommand(UiAutomation automation, String cmd) throws IOException { return new String(runShellCommandByteOutput(automation, cmd)); } /** * Executes a shell command using shell user identity, and return the standard output as a byte * array *

Note: calling this function requires API level 21 or above * * @param automation {@link UiAutomation} instance, obtained from a test running in * instrumentation framework * @param cmd the command to run * @return the standard output of the command as a byte array */ static byte[] runShellCommandByteOutput(UiAutomation automation, String cmd) throws IOException { checkCommandBeforeRunning(cmd); ParcelFileDescriptor pfd = automation.executeShellCommand(cmd); try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd)) { return FileUtils.readInputStreamFully(fis); } } private static void checkCommandBeforeRunning(String cmd) { Log.v(TAG, "Running command: " + cmd); if (cmd.startsWith("pm grant ") || cmd.startsWith("pm revoke ")) { throw new UnsupportedOperationException("Use UiAutomation.grantRuntimePermission() " + "or revokeRuntimePermission() directly, which are more robust."); } } /** * Simpler version of {@link #runShellCommand(Instrumentation, String)}. */ public static String runShellCommand(String cmd) { try { return runShellCommand(InstrumentationRegistry.getInstrumentation(), cmd); } catch (IOException e) { fail("Failed reading command output: " + e); return ""; } } /** * Like {@link #runShellCommand(String)} but throws if anything was printed to stderr on S+, and * delegates to {@link #runShellCommand(String)} on older platforms for compatibility. */ public static String runShellCommandOrThrow(String cmd) { if (!SdkLevel.isAtLeastS()) { return runShellCommand(cmd); } UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); try { checkCommandBeforeRunning(cmd); ParcelFileDescriptor[] fds = automation.executeShellCommandRwe(cmd); ParcelFileDescriptor fdOut = fds[0]; ParcelFileDescriptor fdIn = fds[1]; ParcelFileDescriptor fdErr = fds[2]; if (fdIn != null) { try { // not using stdin fdIn.close(); } catch (Exception e) { // Ignore } } String out; String err; try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdOut)) { out = new String(FileUtils.readInputStreamFully(fis)); } try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdErr)) { err = new String(FileUtils.readInputStreamFully(fis)); } if (!err.isEmpty()) { fail("Command failed:\n$ " + cmd + "\n\nstderr:\n" + err + "\n\nstdout:\n" + out); } return out; } catch (IOException e) { fail("Failed reading command output: " + e); return ""; } } /** * Same as {@link #runShellCommand(String)}, with optionally * check the result using {@code resultChecker}. */ public static String runShellCommand(String cmd, Predicate resultChecker) { final String result = runShellCommand(cmd); if (resultChecker != null) { assertTrue("Assertion failed. Command was: " + cmd + "\n" + "Output was:\n" + result, resultChecker.test(result)); } return result; } /** * Same as {@link #runShellCommand(String)}, but fails if the output is not empty. */ public static String runShellCommandForNoOutput(String cmd) { final String result = runShellCommand(cmd); assertTrue("Command failed. Command was: " + cmd + "\n" + "Didn't expect any output, but the output was:\n" + result, result.length() == 0); return result; } /** * Runs a command and print the result on logcat. */ public static void runCommandAndPrintOnLogcat(String logtag, String cmd) { Log.i(logtag, "Executing: " + cmd); final String output = runShellCommand(cmd); for (String line : output.split("\\n", -1)) { Log.i(logtag, line); } } /** * Runs a command and return the section matching the patterns. * * @see TextUtils#extractSection */ public static String runCommandAndExtractSection(String cmd, String extractionStartRegex, boolean startInclusive, String extractionEndRegex, boolean endInclusive) { return TextUtils.extractSection(runShellCommand(cmd), extractionStartRegex, startInclusive, extractionEndRegex, endInclusive); } /** * Runs a {@link ThrowingSupplier} adopting Shell's permissions, and returning the result. */ public static T runWithShellPermissionIdentity(@NonNull ThrowingSupplier supplier) { final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation(); AtomicReference result = new AtomicReference<>(); runWithShellPermissionIdentity(automan, () -> result.set(supplier.get())); return result.get(); } /** * Runs a {@link ThrowingSupplier} adopting a subset of Shell's permissions, * and returning the result. */ public static T runWithShellPermissionIdentity(@NonNull ThrowingSupplier supplier, String... permissions) { final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation(); AtomicReference result = new AtomicReference<>(); runWithShellPermissionIdentity(automan, () -> result.set(supplier.get()), permissions); return result.get(); } /** * Runs a {@link ThrowingRunnable} adopting Shell's permissions. */ public static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable) { final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation(); runWithShellPermissionIdentity(automan, runnable); } /** * Runs a {@link ThrowingRunnable} adopting a subset of Shell's permissions. */ public static void runWithShellPermissionIdentity(@NonNull ThrowingRunnable runnable, String... permissions) { final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation(); runWithShellPermissionIdentity(automan, runnable, permissions); } /** * Runs a {@link ThrowingRunnable} adopting Shell's permissions, where you can specify the * uiAutomation used. */ public static void runWithShellPermissionIdentity( @NonNull UiAutomation automan, @NonNull ThrowingRunnable runnable) { runWithShellPermissionIdentity(automan, runnable, null /* permissions */); } /** * Runs a {@link ThrowingRunnable} adopting Shell's permissions, where you can specify the * uiAutomation used. * @param automan UIAutomation to use. * @param runnable The code to run with Shell's identity. * @param permissions A subset of Shell's permissions. Passing {@code null} will use all * available permissions. */ public static void runWithShellPermissionIdentity(@NonNull UiAutomation automan, @NonNull ThrowingRunnable runnable, String... permissions) { automan.adoptShellPermissionIdentity(permissions); try { runnable.run(); } catch (Exception e) { throw new RuntimeException("Caught exception", e); } finally { automan.dropShellPermissionIdentity(); } } /** * Calls a {@link Callable} adopting Shell's permissions. */ public static T callWithShellPermissionIdentity(@NonNull Callable callable) throws Exception { final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation(); automan.adoptShellPermissionIdentity(); try { return callable.call(); } finally { automan.dropShellPermissionIdentity(); } } /** * Calls a {@link Callable} adopting Shell's permissions. * * @param callable The code to call with Shell's identity. * @param permissions A subset of Shell's permissions. Passing {@code null} will use all * available permissions. */ public static T callWithShellPermissionIdentity(@NonNull Callable callable, String... permissions) throws Exception { final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation(); automan.adoptShellPermissionIdentity(permissions); try { return callable.call(); } finally { automan.dropShellPermissionIdentity(); } } /** * Make sure that a {@link Runnable} eventually finishes without throwing a {@link * Exception}. * * @param r The {@link Runnable} to run. */ public static void eventually(@NonNull ThrowingRunnable r) { eventually(r, TIMEOUT_MILLIS); } /** * Make sure that a {@link Runnable} eventually finishes without throwing a {@link * Exception}. * * @param r The {@link Runnable} to run. * @param r The number of milliseconds to wait for r to not throw */ public static void eventually(@NonNull ThrowingRunnable r, long timeoutMillis) { long start = System.currentTimeMillis(); while (true) { try { r.run(); return; } catch (Throwable e) { if (System.currentTimeMillis() - start < timeoutMillis) { try { Thread.sleep(100); } catch (InterruptedException ignored) { throw new RuntimeException(e); } } else { throw new RuntimeException(e); } } } } /** * Make sure that a {@link Callable} eventually finishes without throwing a {@link * Exception}. * * @param c The {@link Callable} to run. * * @return The return value of {@code c} */ public static T getEventually(@NonNull Callable c) throws Exception { return getEventually(c, TIMEOUT_MILLIS); } /** * Make sure that a {@link Callable} eventually finishes without throwing a {@link * Exception}. * * @param c The {@link Callable} to run. * @param timeoutMillis The number of milliseconds to wait for r to not throw * * @return The return value of {@code c} */ public static T getEventually(@NonNull Callable c, long timeoutMillis) throws Exception { long start = System.currentTimeMillis(); while (true) { try { return c.call(); } catch (Throwable e) { if (System.currentTimeMillis() - start < timeoutMillis) { try { Thread.sleep(100); } catch (InterruptedException ignored) { throw new RuntimeException(e); } } else { throw e; } } } } }