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.car.test.AbstractExpectableTestCase; 32 import android.car.test.ApiCheckerRule; 33 import android.content.ComponentName; 34 import android.content.Context; 35 import android.content.ServiceConnection; 36 import android.hardware.automotive.vehicle.InitialUserInfoRequestType; 37 import android.os.Build; 38 import android.os.IBinder; 39 import android.os.Looper; 40 import android.os.ParcelFileDescriptor; 41 import android.os.PowerManager; 42 import android.os.SystemClock; 43 import android.util.Log; 44 45 import androidx.test.platform.app.InstrumentationRegistry; 46 47 import org.junit.After; 48 import org.junit.Before; 49 import org.junit.Rule; 50 51 import java.io.BufferedReader; 52 import java.io.ByteArrayOutputStream; 53 import java.io.FileDescriptor; 54 import java.io.FileInputStream; 55 import java.io.IOException; 56 import java.io.InputStream; 57 import java.io.InputStreamReader; 58 import java.util.Collection; 59 import java.util.concurrent.Semaphore; 60 import java.util.concurrent.TimeUnit; 61 62 /** 63 * Base class for tests that don't need to connect to a {@link android.car.Car} object. 64 * 65 * <p>For tests that don't need a {@link android.car.Car} object, use 66 * {@link CarLessApiTestBase} instead. 67 */ 68 public abstract class CarApiTestBase extends AbstractExpectableTestCase { 69 70 private static final String TAG = CarApiTestBase.class.getSimpleName(); 71 72 protected static final long DEFAULT_WAIT_TIMEOUT_MS = 120_000; 73 74 /** 75 * Constant used to wait blindly, when there is no condition that can be checked. 76 */ 77 private static final int SUSPEND_TIMEOUT_MS = 5_000; 78 79 /** 80 * How long to sleep (multiple times) while waiting for a condition. 81 */ 82 private static final int SMALL_NAP_MS = 100; 83 84 protected static final ReceiverTrackingContext sContext = new ReceiverTrackingContext( 85 InstrumentationRegistry.getInstrumentation().getTargetContext()); 86 87 private Car mCar; 88 89 protected final DefaultServiceConnectionListener mConnectionListener = 90 new DefaultServiceConnectionListener(); 91 92 // TODO(b/242350638): temporary hack to allow subclasses to disable checks - should be removed 93 // when not needed anymore 94 private final ApiCheckerRule.Builder mApiCheckerRuleBuilder = new ApiCheckerRule.Builder(); 95 96 @Rule 97 public final ApiCheckerRule mApiCheckerRule; 98 99 // TODO(b/242350638): temporary hack to allow subclasses to disable checks - should be removed 100 // when not needed anymore CarApiTestBase()101 protected CarApiTestBase() { 102 configApiCheckerRule(mApiCheckerRuleBuilder); 103 mApiCheckerRule = mApiCheckerRuleBuilder.build(); 104 } 105 106 // TODO(b/242350638): temporary hack to allow subclasses to disable checks - should be removed 107 // when not needed anymore configApiCheckerRule(ApiCheckerRule.Builder builder)108 protected void configApiCheckerRule(ApiCheckerRule.Builder builder) { 109 Log.v(TAG, "Good News, Everyone! Class " + getClass() 110 + " doesn't override configApiCheckerRule()"); 111 } 112 113 @Before setFixturesAndConnectToCar()114 public final void setFixturesAndConnectToCar() throws Exception { 115 Log.d(TAG, "setFixturesAndConnectToCar() for " + getTestName()); 116 117 mCar = Car.createCar(getContext(), mConnectionListener); 118 mCar.connect(); 119 mConnectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS); 120 } 121 122 @Before dontStopUserOnSwitch()123 public final void dontStopUserOnSwitch() throws Exception { 124 Log.d(TAG, "Calling am.setStopUserOnSwitch(false) for " + getTestName()); 125 getContext().getSystemService(ActivityManager.class) 126 .setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_FALSE); 127 } 128 129 @After disconnectCar()130 public final void disconnectCar() throws Exception { 131 if (mCar == null) { 132 Log.wtf(TAG, "no mCar on " + getTestName() + ".tearDown()"); 133 return; 134 } 135 mCar.disconnect(); 136 } 137 138 @After resetStopUserOnSwitch()139 public final void resetStopUserOnSwitch() throws Exception { 140 Log.d(TAG, "Calling am.setStopUserOnSwitch(default) for " + getTestName()); 141 getContext().getSystemService(ActivityManager.class) 142 .setStopUserOnSwitch(ActivityManager.STOP_USER_ON_SWITCH_DEFAULT); 143 } 144 145 @After checkReceiversUnregisters()146 public final void checkReceiversUnregisters() { 147 Collection<String> receivers = sContext.getReceiversInfo(); 148 Log.d(TAG, "Checking if all receivers were unregistered."); 149 150 assertWithMessage("Broadcast receivers that are not unregistered: %s", receivers) 151 .that(receivers).isEmpty(); 152 } 153 getCar()154 protected Car getCar() { 155 return mCar; 156 } 157 getContext()158 protected final Context getContext() { 159 return sContext; 160 } 161 162 @SuppressWarnings("TypeParameterUnusedInFormals") // error prone complains about returning <T> getCarService(@onNull String serviceName)163 protected final <T> T getCarService(@NonNull String serviceName) { 164 assertThat(serviceName).isNotNull(); 165 Object service = mCar.getCarManager(serviceName); 166 assertWithMessage("Could not get service %s", serviceName).that(service).isNotNull(); 167 168 @SuppressWarnings("unchecked") 169 T castService = (T) service; 170 return castService; 171 } 172 assertMainThread()173 protected static void assertMainThread() { 174 assertThat(Looper.getMainLooper().isCurrentThread()).isTrue(); 175 } 176 177 protected static final class DefaultServiceConnectionListener implements ServiceConnection { 178 private final Semaphore mConnectionWait = new Semaphore(0); 179 waitForConnection(long timeoutMs)180 public void waitForConnection(long timeoutMs) throws InterruptedException { 181 mConnectionWait.tryAcquire(timeoutMs, TimeUnit.MILLISECONDS); 182 } 183 184 @Override onServiceConnected(ComponentName name, IBinder service)185 public void onServiceConnected(ComponentName name, IBinder service) { 186 assertMainThread(); 187 mConnectionWait.release(); 188 } 189 190 @Override onServiceDisconnected(ComponentName name)191 public void onServiceDisconnected(ComponentName name) { 192 assertMainThread(); 193 fail("Car service crashed"); 194 } 195 } 196 suspendToRamAndResume()197 protected static void suspendToRamAndResume() 198 throws Exception { 199 Log.d(TAG, "Emulate suspend to RAM and resume"); 200 try { 201 Log.d(TAG, "Disabling background users starting on garage mode"); 202 runShellCommand("cmd car_service set-start-bg-users-on-garage-mode false"); 203 204 PowerManager powerManager = sContext.getSystemService(PowerManager.class); 205 // clear log 206 runShellCommand("logcat -b all -c"); 207 // We use a simulated suspend because physically suspended devices cannot be woken up by 208 // a shell command. 209 runShellCommand("cmd car_service suspend --simulate --skip-garagemode " 210 + "--wakeup-after 3"); 211 // Check for suspend success 212 waitUntil("screen is still on after suspend", 213 SUSPEND_TIMEOUT_MS, () -> !powerManager.isScreenOn()); 214 215 // The device will resume after 3 seconds. 216 waitForLogcatMessage("logcat -b events", "car_user_svc_initial_user_info_req_complete: " 217 + InitialUserInfoRequestType.RESUME, 60_000); 218 } catch (Exception e) { 219 runShellCommand("cmd car_service set-start-bg-users-on-garage-mode true"); 220 } 221 } 222 223 /** 224 * Wait for a particular logcat message. 225 * 226 * @param cmd is logcat command with buffer or filters 227 * @param match is the string to be found in the log 228 * @param timeoutMs for which call should wait for the match 229 */ waitForLogcatMessage(String cmd, String match, int timeoutMs)230 protected static void waitForLogcatMessage(String cmd, String match, int timeoutMs) { 231 Log.d(TAG, "waiting for logcat match: " + match); 232 long startTime = SystemClock.elapsedRealtime(); 233 UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 234 ParcelFileDescriptor output = automation.executeShellCommand(cmd); 235 FileDescriptor fd = output.getFileDescriptor(); 236 FileInputStream fileInputStream = new FileInputStream(fd); 237 try (BufferedReader bufferedReader = new BufferedReader( 238 new InputStreamReader(fileInputStream))) { 239 String line; 240 while ((line = bufferedReader.readLine()) != null) { 241 if (line.contains(match)) { 242 Log.d(TAG, "match found in " 243 + (SystemClock.elapsedRealtime() - startTime) + " ms"); 244 break; 245 } 246 if ((SystemClock.elapsedRealtime() - startTime) > timeoutMs) { 247 fail("logcat message(%s) not found in %d ms", match, timeoutMs); 248 } 249 } 250 } catch (IOException e) { 251 fail("match (%s) was not found, IO exception: %s", match, e); 252 } 253 } 254 waitUntil(String msg, long timeoutMs, BooleanSupplierWithThrow condition)255 protected static boolean waitUntil(String msg, long timeoutMs, 256 BooleanSupplierWithThrow condition) { 257 long deadline = SystemClock.elapsedRealtime() + timeoutMs; 258 do { 259 try { 260 if (condition.getAsBoolean()) { 261 return true; 262 } 263 } catch (Exception e) { 264 Log.e(TAG, "Exception in waitUntil: " + msg); 265 throw new RuntimeException(e); 266 } 267 SystemClock.sleep(SMALL_NAP_MS); 268 } while (SystemClock.elapsedRealtime() < deadline); 269 270 fail("%s after %d ms", msg, timeoutMs); 271 return false; 272 } 273 274 // TODO(b/250914846): Clean this up once the investigation is done. 275 // Same as waitUntil except for not failing the test. waitUntilNoFail(long timeoutMs, BooleanSupplierWithThrow condition)276 protected static boolean waitUntilNoFail(long timeoutMs, 277 BooleanSupplierWithThrow condition) { 278 long deadline = SystemClock.elapsedRealtime() + timeoutMs; 279 do { 280 try { 281 if (condition.getAsBoolean()) { 282 return true; 283 } 284 } catch (Exception e) { 285 Log.e(TAG, "Exception in waitUntilNoFail"); 286 throw new RuntimeException(e); 287 } 288 SystemClock.sleep(SMALL_NAP_MS); 289 } while (SystemClock.elapsedRealtime() < deadline); 290 291 return false; 292 } 293 requireNonUserBuild()294 protected void requireNonUserBuild() { 295 assumeFalse("Requires Shell commands that are not available on user builds", Build.IS_USER); 296 } 297 getTestName()298 protected String getTestName() { 299 return getClass().getSimpleName() + "." + mApiCheckerRule.getTestMethodName(); 300 } 301 fail(String format, Object...args)302 protected static void fail(String format, Object...args) { 303 String message = String.format(format, args); 304 Log.e(TAG, "test failed: " + message); 305 org.junit.Assert.fail(message); 306 } 307 executeShellCommand(String commandFormat, Object... args)308 protected static String executeShellCommand(String commandFormat, Object... args) 309 throws IOException { 310 UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation(); 311 return executeShellCommand(uiAutomation, commandFormat, args); 312 } 313 executeShellCommand(UiAutomation uiAutomation, String commandFormat, Object... args)314 private static String executeShellCommand(UiAutomation uiAutomation, String commandFormat, 315 Object... args) throws IOException { 316 ParcelFileDescriptor stdout = uiAutomation.executeShellCommand( 317 String.format(commandFormat, args)); 318 try (InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(stdout)) { 319 ByteArrayOutputStream result = new ByteArrayOutputStream(); 320 byte[] buffer = new byte[1024]; 321 int length; 322 while ((length = inputStream.read(buffer)) != -1) { 323 result.write(buffer, 0, length); 324 } 325 return result.toString("UTF-8"); 326 } 327 } 328 } 329