• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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