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