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