1 /*
2  * Copyright (C) 2020 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 com.android.car.pm;
18 
19 import static androidx.car.app.activity.CarAppActivity.ACTION_SHOW_DIALOG;
20 import static androidx.car.app.activity.CarAppActivity.ACTION_START_SECOND_INSTANCE;
21 import static androidx.car.app.activity.CarAppActivity.SECOND_INSTANCE_TITLE;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import static org.junit.Assert.assertNotNull;
26 
27 import android.app.Activity;
28 import android.app.ActivityOptions;
29 import android.app.AlertDialog;
30 import android.app.UiAutomation;
31 import android.car.Car;
32 import android.car.content.pm.CarPackageManager;
33 import android.car.drivingstate.CarDrivingStateEvent;
34 import android.car.drivingstate.CarDrivingStateManager;
35 import android.content.ComponentName;
36 import android.content.Context;
37 import android.content.Intent;
38 import android.os.Bundle;
39 import android.support.test.uiautomator.By;
40 import android.support.test.uiautomator.Configurator;
41 import android.support.test.uiautomator.UiDevice;
42 import android.support.test.uiautomator.Until;
43 import android.view.Display;
44 
45 import androidx.car.app.activity.CarAppActivity;
46 import androidx.test.InstrumentationRegistry;
47 import androidx.test.ext.junit.runners.AndroidJUnit4;
48 import androidx.test.filters.MediumTest;
49 
50 import org.junit.After;
51 import org.junit.Before;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 
55 import java.util.concurrent.CountDownLatch;
56 import java.util.concurrent.TimeUnit;
57 import java.util.concurrent.atomic.AtomicReference;
58 
59 @RunWith(AndroidJUnit4.class)
60 @MediumTest
61 public class ActivityBlockingActivityTest {
62     private static final String ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID =
63             "com.android.car:id/blocking_text";
64 
65     // cf_x86_auto is very slow, so uses very long timeout.
66     private static final int UI_TIMEOUT_MS = 20_000;
67     private static final int NOT_FOUND_UI_TIMEOUT_MS = 10_000;
68     private static final long ACTIVITY_TIMEOUT_MS = 5000;
69 
70     private CarDrivingStateManager mCarDrivingStateManager;
71     private CarPackageManager mCarPackageManager;
72 
73     private UiDevice mDevice;
74 
75     // NOTE: Assume there is only one testing Activity.
76     private static final AtomicReference<TempActivity> sTestingActivity = new AtomicReference<>();
77 
78     @Before
setUp()79     public void setUp() throws Exception {
80         Car car = Car.createCar(getContext());
81         mCarDrivingStateManager = (CarDrivingStateManager)
82                 car.getCarManager(Car.CAR_DRIVING_STATE_SERVICE);
83         mCarPackageManager = (CarPackageManager)
84                 car.getCarManager(Car.PACKAGE_SERVICE);
85         assertNotNull(mCarDrivingStateManager);
86 
87         Configurator.getInstance()
88                 .setUiAutomationFlags(UiAutomation.FLAG_DONT_SUPPRESS_ACCESSIBILITY_SERVICES);
89         mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
90 
91         setDrivingStateMoving();
92     }
93 
94     @After
tearDown()95     public void tearDown() throws Exception {
96         setDrivingStateParked();
97 
98         TempActivity testingActivity = sTestingActivity.get();
99         if (testingActivity != null) {
100             testingActivity.finishCompletely();
101         }
102     }
103 
104     @Test
testBlockingActivity_doActivity_isNotBlocked()105     public void testBlockingActivity_doActivity_isNotBlocked() throws Exception {
106         startActivity(toComponentName(getTestContext(), DoActivity.class));
107 
108         assertThat(mDevice.wait(Until.findObject(By.text(
109                 DoActivity.class.getSimpleName())),
110                 UI_TIMEOUT_MS)).isNotNull();
111         assertBlockingActivityNotFound();
112     }
113 
114     @Test
testBlockingActivity_doActivity_showingDialog_isNotBlocked()115     public void testBlockingActivity_doActivity_showingDialog_isNotBlocked() throws Exception {
116         Intent intent = new Intent();
117         intent.putExtra(DoActivity.INTENT_EXTRA_SHOW_DIALOG, true);
118         intent.setComponent(toComponentName(getTestContext(), DoActivity.class));
119         startActivity(intent);
120 
121         assertThat(mDevice.wait(Until.findObject(By.text(
122                 DoActivity.DIALOG_TITLE)),
123                 UI_TIMEOUT_MS)).isNotNull();
124         assertBlockingActivityNotFound();
125     }
126 
127     @Test
testBlockingActivity_doTemplateActivity_isNotBlocked()128     public void testBlockingActivity_doTemplateActivity_isNotBlocked() throws Exception {
129         startActivity(toComponentName(getTestContext(), CarAppActivity.class));
130 
131         assertThat(mDevice.wait(Until.findObject(By.text(
132                 CarAppActivity.class.getSimpleName())),
133                 UI_TIMEOUT_MS)).isNotNull();
134         assertBlockingActivityNotFound();
135     }
136 
137     @Test
testBlockingActivity_multipleDoTemplateActivity_notBlocked()138     public void testBlockingActivity_multipleDoTemplateActivity_notBlocked() throws Exception {
139         startActivity(toComponentName(getTestContext(), CarAppActivity.class));
140         assertThat(mDevice.wait(Until.findObject(By.text(
141                 CarAppActivity.class.getSimpleName())),
142                 UI_TIMEOUT_MS)).isNotNull();
143         getContext().sendBroadcast(new Intent().setAction(ACTION_START_SECOND_INSTANCE));
144         assertThat(mDevice.wait(Until.findObject(By.text(
145                 SECOND_INSTANCE_TITLE)),
146                 UI_TIMEOUT_MS)).isNotNull();
147         assertBlockingActivityNotFound();
148     }
149 
150     @Test
testBlockingActivity_doTemplateActivity_showingDialog_isBlocked()151     public void testBlockingActivity_doTemplateActivity_showingDialog_isBlocked() throws Exception {
152         startActivity(toComponentName(getTestContext(), CarAppActivity.class));
153         assertThat(mDevice.wait(Until.findObject(By.text(
154                 CarAppActivity.class.getSimpleName())),
155                 UI_TIMEOUT_MS)).isNotNull();
156         assertBlockingActivityNotFound();
157         assertThat(mCarPackageManager.isActivityDistractionOptimized(
158                 getTestContext().getPackageName(),
159                 CarAppActivity.class.getName()
160         )).isTrue();
161 
162         getContext().sendBroadcast(new Intent().setAction(ACTION_SHOW_DIALOG));
163         assertThat(mDevice.wait(Until.findObject(By.text(DoActivity.DIALOG_TITLE)),
164                 UI_TIMEOUT_MS)).isNotNull();
165 
166         assertThat(mDevice.wait(Until.findObject(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)),
167                 UI_TIMEOUT_MS)).isNotNull();
168         assertThat(mCarPackageManager.isActivityDistractionOptimized(
169                 getTestContext().getPackageName(),
170                 CarAppActivity.class.getName()
171         )).isFalse();
172     }
173 
174     @Test
testBlockingActivity_nonDoActivity_isBlocked()175     public void testBlockingActivity_nonDoActivity_isBlocked() throws Exception {
176         startNonDoActivity(NonDoActivity.EXTRA_DO_NOTHING);
177 
178         assertThat(mDevice.wait(Until.findObject(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)),
179                 UI_TIMEOUT_MS)).isNotNull();
180     }
181 
182     @Test
testBlockingActivity_nonDoFinishesOnCreate_noBlockingActivity()183     public void testBlockingActivity_nonDoFinishesOnCreate_noBlockingActivity()
184             throws Exception {
185         startNonDoActivity(NonDoActivity.EXTRA_ONCREATE_FINISH_IMMEDIATELY);
186 
187         assertBlockingActivityNotFound();
188     }
189 
190     @Test
testBlockingActivity_nonDoLaunchesDoOnCreate_noBlockingActivity()191     public void testBlockingActivity_nonDoLaunchesDoOnCreate_noBlockingActivity()
192             throws Exception {
193         startNonDoActivity(NonDoActivity.EXTRA_ONCREATE_LAUNCH_DO_IMMEDIATELY);
194 
195         assertBlockingActivityNotFound();
196     }
197 
198     @Test
testBlockingActivity_nonDoFinishesOnResume_noBlockingActivity()199     public void testBlockingActivity_nonDoFinishesOnResume_noBlockingActivity()
200             throws Exception {
201         startNonDoActivity(NonDoActivity.EXTRA_ONRESUME_FINISH_IMMEDIATELY);
202 
203         assertBlockingActivityNotFound();
204     }
205 
206     @Test
testBlockingActivity_nonDoLaunchesDoOnResume_noBlockingActivity()207     public void testBlockingActivity_nonDoLaunchesDoOnResume_noBlockingActivity()
208             throws Exception {
209         startNonDoActivity(NonDoActivity.EXTRA_ONRESUME_LAUNCH_DO_IMMEDIATELY);
210 
211         assertBlockingActivityNotFound();
212     }
213 
214     @Test
testBlockingActivity_nonDoNoHistory_isBlocked()215     public void testBlockingActivity_nonDoNoHistory_isBlocked() throws Exception {
216         startActivity(toComponentName(getTestContext(), NonDoNoHistoryActivity.class));
217 
218         assertThat(mDevice.wait(Until.findObject(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)),
219                 UI_TIMEOUT_MS)).isNotNull();
220     }
221 
assertBlockingActivityNotFound()222     private void assertBlockingActivityNotFound() {
223         assertThat(mDevice.wait(Until.gone(By.res(ACTIVITY_BLOCKING_ACTIVITY_TEXTVIEW_ID)),
224                 NOT_FOUND_UI_TIMEOUT_MS)).isNotNull();
225     }
226 
startActivity(ComponentName name)227     private void startActivity(ComponentName name) {
228         Intent intent = new Intent();
229         intent.setComponent(name);
230         startActivity(intent);
231     }
232 
startActivity(Intent intent)233     private void startActivity(Intent intent) {
234         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
235         ActivityOptions options = ActivityOptions.makeBasic();
236         options.setLaunchDisplayId(Display.DEFAULT_DISPLAY);
237         getContext().startActivity(intent, options.toBundle());
238     }
239 
startNonDoActivity(int firstActionFlag)240     private void startNonDoActivity(int firstActionFlag) {
241         ComponentName activity = toComponentName(getTestContext(), NonDoActivity.class);
242         Intent intent = new Intent();
243         intent.setComponent(activity);
244         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
245         intent.putExtra(NonDoActivity.EXTRA_FIRST_ACTION, firstActionFlag);
246 
247         ActivityOptions options = ActivityOptions.makeBasic();
248         options.setLaunchDisplayId(Display.DEFAULT_DISPLAY);
249 
250         getContext().startActivity(intent, options.toBundle());
251     }
252 
253 
setDrivingStateMoving()254     private void setDrivingStateMoving() {
255         mCarDrivingStateManager.injectDrivingState(CarDrivingStateEvent.DRIVING_STATE_MOVING);
256     }
257 
setDrivingStateParked()258     private void setDrivingStateParked() {
259         mCarDrivingStateManager.injectDrivingState(CarDrivingStateEvent.DRIVING_STATE_PARKED);
260     }
261 
toComponentName(Context ctx, Class<?> cls)262     private static ComponentName toComponentName(Context ctx, Class<?> cls) {
263         return ComponentName.createRelative(ctx, cls.getName());
264     }
265 
266     public static class NonDoActivity extends TempActivity {
267 
268         static final String EXTRA_FIRST_ACTION = "first_action";
269 
270         static final int EXTRA_DO_NOTHING = 0;
271         static final int EXTRA_ONCREATE_FINISH_IMMEDIATELY = 1;
272         static final int EXTRA_ONCREATE_LAUNCH_DO_IMMEDIATELY = 2;
273         static final int EXTRA_ONRESUME_FINISH_IMMEDIATELY = 3;
274         static final int EXTRA_ONRESUME_LAUNCH_DO_IMMEDIATELY = 4;
275 
276         @Override
onCreate(Bundle savedInstanceState)277         protected void onCreate(Bundle savedInstanceState) {
278             super.onCreate(savedInstanceState);
279             Bundle extras = getIntent().getExtras();
280             if (extras != null) {
281                 switch (extras.getInt(EXTRA_FIRST_ACTION, EXTRA_DO_NOTHING)) {
282                     case EXTRA_ONCREATE_LAUNCH_DO_IMMEDIATELY:
283                         startActivity(new Intent(this, DoActivity.class));
284                         finish();
285                         break;
286                     case EXTRA_ONCREATE_FINISH_IMMEDIATELY:
287                         finish();
288                         break;
289                     default:
290                         // do nothing
291                 }
292             }
293         }
294 
295         @Override
onResume()296         protected void onResume() {
297             super.onResume();
298             Bundle extras = getIntent().getExtras();
299             if (extras != null) {
300                 switch (extras.getInt(EXTRA_FIRST_ACTION, EXTRA_DO_NOTHING)) {
301                     case EXTRA_ONRESUME_LAUNCH_DO_IMMEDIATELY:
302                         startActivity(new Intent(this, DoActivity.class));
303                         finish();
304                         break;
305                     case EXTRA_ONRESUME_FINISH_IMMEDIATELY:
306                         finish();
307                         break;
308                     default:
309                         // do nothing
310                 }
311             }
312         }
313     }
314 
315     public static class NonDoNoHistoryActivity extends TempActivity {
316     }
317 
318     public static class DoActivity extends TempActivity {
319         public static final String INTENT_EXTRA_SHOW_DIALOG = "SHOW_DIALOG";
320         public static final String DIALOG_TITLE = "Title";
321 
322         @Override
onCreate(Bundle savedInstanceState)323         protected void onCreate(Bundle savedInstanceState) {
324             super.onCreate(savedInstanceState);
325             if (getIntent().getBooleanExtra(INTENT_EXTRA_SHOW_DIALOG, false)) {
326                 AlertDialog dialog = new AlertDialog.Builder(DoActivity.this)
327                         .setTitle(DIALOG_TITLE)
328                         .setMessage("Message")
329                         .create();
330                 dialog.show();
331             }
332         }
333     }
334 
335     /** Activity that closes itself after some timeout to clean up the screen. */
336     public static class TempActivity extends Activity {
337         private final CountDownLatch mDestroyed = new CountDownLatch(1);
338         @Override
onCreate(Bundle savedInstanceState)339         protected void onCreate(Bundle savedInstanceState) {
340             super.onCreate(savedInstanceState);
341             sTestingActivity.set(this);
342         }
343 
344         @Override
onDestroy()345         protected void onDestroy() {
346             sTestingActivity.set(null);
347             super.onDestroy();
348             mDestroyed.countDown();
349         }
350 
finishCompletely()351         void finishCompletely() throws InterruptedException {
352             finish();
353             mDestroyed.await(ACTIVITY_TIMEOUT_MS, TimeUnit.MILLISECONDS);
354         }
355     }
356 
getContext()357     private Context getContext() {
358         return InstrumentationRegistry.getInstrumentation().getTargetContext();
359     }
360 
getTestContext()361     private Context getTestContext() {
362         return InstrumentationRegistry.getInstrumentation().getContext();
363     }
364 }
365