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