blob: 0b70c8b000ca49ef9fada03c0a8f27739215c906 [file] [log] [blame]
/*
* 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
* <p>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
* <p>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
* <p>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<String> 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> T runWithShellPermissionIdentity(@NonNull ThrowingSupplier<T> supplier) {
final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
AtomicReference<T> 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> T runWithShellPermissionIdentity(@NonNull ThrowingSupplier<T> supplier,
String... permissions) {
final UiAutomation automan = InstrumentationRegistry.getInstrumentation().getUiAutomation();
AtomicReference<T> 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> T callWithShellPermissionIdentity(@NonNull Callable<T> 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> T callWithShellPermissionIdentity(@NonNull Callable<T> 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> T getEventually(@NonNull Callable<T> 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> T getEventually(@NonNull Callable<T> 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;
}
}
}
}
}