1 /* 2 * Copyright (C) 2015 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 android.car.apitest; 18 19 import static com.android.compatibility.common.util.ShellUtils.runShellCommand; 20 import static com.android.compatibility.common.util.TestUtils.BooleanSupplierWithThrow; 21 22 import static com.google.common.truth.Truth.assertThat; 23 import static com.google.common.truth.Truth.assertWithMessage; 24 25 import static org.junit.Assume.assumeFalse; 26 27 import android.annotation.NonNull; 28 import android.app.ActivityManager; 29 import android.app.UiAutomation; 30 import android.car.Car; 31 import android.content.ComponentName; 32 import android.content.Context; 33 import android.content.ServiceConnection; 34 import android.hardware.automotive.vehicle.InitialUserInfoRequestType; 35 import android.os.Build; 36 import android.os.IBinder; 37 import android.os.Looper; 38 import android.os.ParcelFileDescriptor; 39 import android.os.PowerManager; 40 import android.os.SystemClock; 41 import android.util.Log; 42 43 import androidx.test.platform.app.InstrumentationRegistry; 44 45 import org.junit.After; 46 import org.junit.Before; 47 import org.junit.Rule; 48 import org.junit.rules.TestName; 49 50 import java.io.BufferedReader; 51 import java.io.ByteArrayOutputStream; 52 import java.io.FileDescriptor; 53 import java.io.FileInputStream; 54 import java.io.IOException; 55 import java.io.InputStream; 56 import java.io.InputStreamReader; 57 import java.util.concurrent.Semaphore; 58 import java.util.concurrent.TimeUnit; 59 60 public abstract class CarApiTestBase { 61 62 private static final String TAG = CarApiTestBase.class.getSimpleName(); 63 64 protected static final long DEFAULT_WAIT_TIMEOUT_MS = 120_000; 65 66 /** 67 * Constant used to wait blindly, when there is no condition that can be checked. 68 */ 69 private static final int SUSPEND_TIMEOUT_MS = 5_000; 70 71 /** 72 * How long to sleep (multiple times) while waiting for a condition. 73 */ 74 private static final int SMALL_NAP_MS = 100; 75 76 protected static final Context sContext = InstrumentationRegistry.getInstrumentation() 77 .getTargetContext(); 78 79 private Car mCar; 80 81 protected final DefaultServiceConnectionListener mConnectionListener = 82 new DefaultServiceConnectionListener(); 83 84 // NOTE: public as required by JUnit; tests should call getTestName() instead 85 @Rule 86 public final TestName mTestName = new TestName(); 87 88 @Before setFixturesAndConnectToCar()89 public final void setFixturesAndConnectToCar() throws Exception { 90 Log.d(TAG, "setFixturesAndConnectToCar() for " + mTestName.getMethodName()); 91 92 mCar = Car.createCar(getContext(), mConnectionListener); 93 mCar.connect(); 94 mConnectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); 95 } 96 97 @Before dontStopUserOnSwitch()98 public final void dontStopUserOnSwitch() throws Exception { 99 Log.d(TAG, "Calling am.setStopUserOnSwitch(false) for " + mTestName.getMethodName()); 100 getContext().getSystemService(ActivityManager.class) 101 .setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_FALSE); 102 } 103 104 @After disconnectCar()105 public final void disconnectCar() throws Exception { 106 if (mCar == null) { 107 Log.wtf(TAG, "no mCar on " + getTestName() + ".tearDown()"); 108 return; 109 } 110 mCar.disconnect(); 111 } 112 113 @After resetStopUserOnSwitch()114 public final void resetStopUserOnSwitch() throws Exception { 115 Log.d(TAG, "Calling am.setStopUserOnSwitch(default) for " + mTestName.getMethodName()); 116 getContext().getSystemService(ActivityManager.class) 117 .setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_DEFAULT); 118 } 119 getCar()120 protected Car getCar() { 121 return mCar; 122 } 123 getContext()124 protected final Context getContext() { 125 return sContext; 126 } 127 128 @SuppressWarnings("TypeParameterUnusedInFormals") // error prone complains about returning <T> getCarService(@onNull String serviceName)129 protected final <T> T getCarService(@NonNull String serviceName) { 130 assertThat(serviceName).isNotNull(); 131 Object service = mCar.getCarManager(serviceName); 132 assertWithMessage("Could not get service %s", serviceName).that(service).isNotNull(); 133 134 @SuppressWarnings("unchecked") 135 T castService = (T) service; 136 return castService; 137 } 138 assertMainThread()139 protected static void assertMainThread() { 140 assertThat(Looper.getMainLooper().isCurrentThread()).isTrue(); 141 } 142 143 protected static final class DefaultServiceConnectionListener implements ServiceConnection { 144 private final Semaphore mConnectionWait = new Semaphore(0); 145 waitForConnection(long timeoutMs)146 public void waitForConnection(long timeoutMs) throws InterruptedException { 147 mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS); 148 } 149 150 @Override onServiceConnected(ComponentName name, IBinder service)151 public void onServiceConnected(ComponentName name, IBinder service) { 152 assertMainThread(); 153 mConnectionWait.release(); 154 } 155 156 @Override onServiceDisconnected(ComponentName name)157 public void onServiceDisconnected(ComponentName name) { 158 assertMainThread(); 159 fail("Car service crashed"); 160 } 161 } 162 suspendToRamAndResume()163 protected static void suspendToRamAndResume() 164 throws Exception { 165 Log.d(TAG, "Emulate suspend to RAM and resume"); 166 try { 167 Log.d(TAG, "Disabling background users starting on garage mode"); 168 runShellCommand("cmd car_service set-start-bg-users-on-garage-mode false"); 169 170 PowerManager powerManager = sContext.getSystemService(PowerManager.class); 171 // clear log 172 runShellCommand("logcat -b all -c"); 173 // We use a simulated suspend because physically suspended devices cannot be woken up by 174 // a shell command. 175 runShellCommand("cmd car_service suspend --simulate --skip-garagemode " 176 + "--wakeup-after 3"); 177 // Check for suspend success 178 waitUntil("screen is still on after suspend", 179 SUSPEND_TIMEOUT_MS, () -> !powerManager.isScreenOn()); 180 181 // The device will resume after 3 seconds. 182 waitForLogcatMessage("logcat -b events", "car_user_svc_initial_user_info_req_complete: " 183 + InitialUserInfoRequestType.RESUME, 60_000); 184 } catch (Exception e) { 185 runShellCommand("cmd car_service set-start-bg-users-on-garage-mode true"); 186 } 187 } 188 189 /** 190 * Wait for a particular logcat message. 191 * 192 * @param cmd is logcat command with buffer or filters 193 * @param match is the string to be found in the log 194 * @param timeoutMs for which call should wait for the match 195 */ waitForLogcatMessage(String cmd, String match, int timeoutMs)196 protected static void waitForLogcatMessage(String cmd, String match, int timeoutMs) { 197 Log.d(TAG, "waiting for logcat match: " + match); 198 long startTime = SystemClock.elapsedRealtime(); 199 UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 200 ParcelFileDescriptor output = automation.executeShellCommand(cmd); 201 FileDescriptor fd = output.getFileDescriptor(); 202 FileInputStream fileInputStream = new FileInputStream(fd); 203 try (BufferedReader bufferedReader = new BufferedReader( 204 new InputStreamReader(fileInputStream))) { 205 String line; 206 while ((line = bufferedReader.readLine()) != null) { 207 if (line.contains(match)) { 208 Log.d(TAG, "match found in " 209 + (SystemClock.elapsedRealtime() - startTime) + " ms"); 210 break; 211 } 212 if ((SystemClock.elapsedRealtime() - startTime) > timeoutMs) { 213 fail("logcat message(%s) not found in %d ms", match, timeoutMs); 214 } 215 } 216 } catch (IOException e) { 217 fail("match (%s) was not found, IO exception: %s", match, e); 218 } 219 } 220 waitUntil(String msg, long timeoutMs, BooleanSupplierWithThrow condition)221 protected static boolean waitUntil(String msg, long timeoutMs, 222 BooleanSupplierWithThrow condition) { 223 long deadline = SystemClock.elapsedRealtime() + timeoutMs; 224 do { 225 try { 226 if (condition.getAsBoolean()) { 227 return true; 228 } 229 } catch (Exception e) { 230 Log.e(TAG, "Exception in waitUntil: " + msg); 231 throw new RuntimeException(e); 232 } 233 SystemClock.sleep(SMALL_NAP_MS); 234 } while (SystemClock.elapsedRealtime() < deadline); 235 236 fail("%s after %d ms", msg, timeoutMs); 237 return false; 238 } 239 requireNonUserBuild()240 protected void requireNonUserBuild() { 241 assumeFalse("Requires Shell commands that are not available on user builds", Build.IS_USER); 242 } 243 getTestName()244 protected String getTestName() { 245 return getClass().getSimpleName() + "." + mTestName.getMethodName(); 246 } 247 fail(String format, Object...args)248 protected static void fail(String format, Object...args) { 249 String message = String.format(format, args); 250 Log.e(TAG, "test failed: " + message); 251 org.junit.Assert.fail(message); 252 } 253 executeShellCommand(String commandFormat, Object... args)254 protected static String executeShellCommand(String commandFormat, Object... args) 255 throws IOException { 256 UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 257 return executeShellCommand(uiAutomation, commandFormat, args); 258 } 259 executeShellCommand(UiAutomation uiAutomation, String commandFormat, Object... args)260 private static String executeShellCommand(UiAutomation uiAutomation, String commandFormat, 261 Object... args) throws IOException { 262 ParcelFileDescriptor stdout = uiAutomation.executeShellCommand( 263 String.format(commandFormat, args)); 264 try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) { 265 ByteArrayOutputStream result = new ByteArrayOutputStream(); 266 byte[] buffer = new byte[1024]; 267 int length; 268 while ((length = inputStream.read(buffer)) != -1) { 269 result.write(buffer, 0, length); 270 } 271 return result.toString("UTF-8"); 272 } 273 } 274 } 275