/* * Copyright (C) 2015 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 android.car.extendedapitest.testbase; import static com.android.compatibility.common.util.ShellUtils.runShellCommand; import static com.android.compatibility.common.util.TestUtils.BooleanSupplierWithThrow; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assume.assumeFalse; import android.annotation.NonNull; import android.app.ActivityManager; import android.app.UiAutomation; import android.car.Car; import android.car.test.AbstractExpectableTestCase; import android.content.ComponentName; import android.content.Context; import android.content.ServiceConnection; import android.hardware.automotive.vehicle.InitialUserInfoRequestType; import android.os.Build; import android.os.IBinder; import android.os.Looper; import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.SystemClock; import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.After; import org.junit.Before; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Collection; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /** * Base class for tests that don't need to connect to a {@link Car} object. * *
For tests that don't need a {@link Car} object, use
* {@link CarLessApiTestBase} instead.
*/
public abstract class CarApiTestBase extends AbstractExpectableTestCase {
private static final String TAG = CarApiTestBase.class.getSimpleName();
protected static final long DEFAULT_WAIT_TIMEOUT_MS = 120_000;
/**
* Constant used to wait blindly, when there is no condition that can be checked.
*/
private static final int SUSPEND_TIMEOUT_MS = 5_000;
/**
* How long to sleep (multiple times) while waiting for a condition.
*/
private static final int SMALL_NAP_MS = 100;
private final ReceiverTrackingContext
mContext = new ReceiverTrackingContext(
InstrumentationRegistry.getInstrumentation().getTargetContext());
private Car mCar;
protected final DefaultServiceConnectionListener mConnectionListener =
new DefaultServiceConnectionListener();
// TODO(b/242350638): temporary hack to allow subclasses to disable checks - should be removed
// when not needed anymore
@Before
public final void setFixturesAndConnectToCar() throws Exception {
Log.d(TAG, "setFixturesAndConnectToCar() for " + getTestName());
mCar = Car.createCar(getContext(), mConnectionListener);
mCar.connect();
mConnectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS);
}
@Before
public final void dontStopUserOnSwitch() throws Exception {
Log.d(TAG, "Calling am.setStopUserOnSwitch(false) for " + getTestName());
getContext().getSystemService(ActivityManager.class)
.setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_FALSE);
}
@After
public final void disconnectCar() throws Exception {
if (mCar == null) {
Log.wtf(TAG, "no mCar on " + getTestName() + ".tearDown()");
return;
}
mCar.disconnect();
}
@After
public final void resetStopUserOnSwitch() throws Exception {
Log.d(TAG, "Calling am.setStopUserOnSwitch(default) for " + getTestName());
getContext().getSystemService(ActivityManager.class)
.setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_DEFAULT);
}
@After
public final void checkReceiversUnregisters() {
Log.d(TAG, "Checking if all receivers were unregistered.");
Collection This method blocks until a connection is available or the timeout expires.
*
* @param timeoutMs the maximum time to wait in milliseconds
* @throws InterruptedException if the current thread is interrupted while waiting
*/
public void waitForConnection(long timeoutMs) throws InterruptedException {
mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS);
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
assertMainThread();
mConnectionWait.release();
}
@Override
public void onServiceDisconnected(ComponentName name) {
assertMainThread();
fail("Car service crashed");
}
}
protected void suspendToRamAndResume() throws Exception {
Log.d(TAG, "Emulate suspend to RAM and resume");
try {
Log.d(TAG, "Disabling background users starting on garage mode");
runShellCommand("cmd car_service set-start-bg-users-on-garage-mode false");
PowerManager powerManager = mContext.getSystemService(PowerManager.class);
// clear log
runShellCommand("logcat -b all -c");
// We use a simulated suspend because physically suspended devices cannot be woken up by
// a shell command.
runShellCommand("cmd car_service suspend --simulate --skip-garagemode "
+ "--wakeup-after 3");
// Check for suspend success
waitUntil("screen is still on after suspend",
SUSPEND_TIMEOUT_MS, () -> !powerManager.isScreenOn());
// The device will resume after 3 seconds.
waitForLogcatMessage("logcat -b events", "car_user_svc_initial_user_info_req_complete: "
+ InitialUserInfoRequestType.RESUME, 60_000);
} catch (Exception e) {
runShellCommand("cmd car_service set-start-bg-users-on-garage-mode true");
}
}
/**
* Wait for a particular logcat message.
*
* @param cmd is logcat command with buffer or filters
* @param match is the string to be found in the log
* @param timeoutMs for which call should wait for the match
*/
protected static void waitForLogcatMessage(String cmd, String match, int timeoutMs) {
Log.d(TAG, "waiting for logcat match: " + match);
long startTime = SystemClock.elapsedRealtime();
UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
ParcelFileDescriptor output = automation.executeShellCommand(cmd);
FileDescriptor fd = output.getFileDescriptor();
FileInputStream fileInputStream = new FileInputStream(fd);
try (BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(fileInputStream))) {
String line;
while ((line = bufferedReader.readLine()) != null) {
if (line.contains(match)) {
Log.d(TAG, "match found in "
+ (SystemClock.elapsedRealtime() - startTime) + " ms");
break;
}
if ((SystemClock.elapsedRealtime() - startTime) > timeoutMs) {
fail("logcat message(%s) not found in %d ms", match, timeoutMs);
}
}
} catch (IOException e) {
fail("match (%s) was not found, IO exception: %s", match, e);
}
}
protected static boolean waitUntil(String msg, long timeoutMs,
BooleanSupplierWithThrow condition) {
long deadline = SystemClock.elapsedRealtime() + timeoutMs;
do {
try {
if (condition.getAsBoolean()) {
return true;
}
} catch (Exception e) {
Log.e(TAG, "Exception in waitUntil: " + msg);
throw new RuntimeException(e);
}
SystemClock.sleep(SMALL_NAP_MS);
} while (SystemClock.elapsedRealtime() < deadline);
fail("%s after %d ms", msg, timeoutMs);
return false;
}
// TODO(b/250914846): Clean this up once the investigation is done.
// Same as waitUntil except for not failing the test.
protected static boolean waitUntilNoFail(long timeoutMs,
BooleanSupplierWithThrow condition) {
long deadline = SystemClock.elapsedRealtime() + timeoutMs;
do {
try {
if (condition.getAsBoolean()) {
return true;
}
} catch (Exception e) {
Log.e(TAG, "Exception in waitUntilNoFail");
throw new RuntimeException(e);
}
SystemClock.sleep(SMALL_NAP_MS);
} while (SystemClock.elapsedRealtime() < deadline);
return false;
}
protected void requireNonUserBuild() {
assumeFalse("Requires Shell commands that are not available on user builds", Build.IS_USER);
}
protected String getTestName() {
// TODO(b/300964422): Add a way to get testName if possible.
return "";
}
protected static void fail(String format, Object...args) {
String message = String.format(format, args);
Log.e(TAG, "test failed: " + message);
org.junit.Assert.fail(message);
}
protected static String executeShellCommand(String commandFormat, Object... args)
throws IOException {
UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
return executeShellCommand(uiAutomation, commandFormat, args);
}
private static String executeShellCommand(UiAutomation uiAutomation, String commandFormat,
Object... args) throws IOException {
ParcelFileDescriptor stdout = uiAutomation.executeShellCommand(
String.format(commandFormat, args));
try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) {
ByteArrayOutputStream result = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
result.write(buffer, 0, length);
}
return result.toString("UTF-8");
}
}
}