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