1 /*
2  * Copyright (C) 2016 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 package com.android.car;
17 
18 import static org.junit.Assert.assertEquals;
19 import static org.junit.Assert.assertTrue;
20 
21 import android.annotation.Nullable;
22 import android.app.Activity;
23 import android.app.ActivityOptions;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.hardware.display.DisplayManager;
28 import android.hardware.display.VirtualDisplay;
29 import android.util.Log;
30 import android.view.Display;
31 
32 import androidx.test.InstrumentationRegistry;
33 import androidx.test.filters.MediumTest;
34 import androidx.test.runner.AndroidJUnit4;
35 
36 import com.android.car.SystemActivityMonitoringService.TopTaskInfoContainer;
37 
38 import org.junit.After;
39 import org.junit.Before;
40 import org.junit.Test;
41 import org.junit.runner.RunWith;
42 
43 import java.util.concurrent.Semaphore;
44 import java.util.concurrent.TimeUnit;
45 import java.util.function.BooleanSupplier;
46 
47 @RunWith(AndroidJUnit4.class)
48 @MediumTest
49 public class SystemActivityMonitoringServiceTest {
50     private static final String TAG = "SystemActivityMonitoringServiceTest";
51 
52     private static final long ACTIVITY_TIMEOUT_MS = 5000;
53     private static final long DEFAULT_TIMEOUT_SECONDS = 2;
54 
55     private SystemActivityMonitoringService mService;
56     private Semaphore mActivityLaunchSemaphore = new Semaphore(0);
57 
58     private final TopTaskInfoContainer[] mTopTaskInfo = new TopTaskInfoContainer[1];
59 
60     @Before
setUp()61     public void setUp() throws Exception {
62         mService = new SystemActivityMonitoringService(getContext());
63         mService.registerActivityLaunchListener(
64                 new FilteredLaunchListener(/* desiredComponent= */ null));
65     }
66 
67     @After
tearDown()68     public void tearDown() throws Exception {
69         mService.registerActivityLaunchListener(null);
70         mService = null;
71     }
72 
73     @Test
testActivityLaunch()74     public void testActivityLaunch() throws Exception {
75         ComponentName activityA = toComponentName(getTestContext(), ActivityA.class);
76         mService.registerActivityLaunchListener(new FilteredLaunchListener(activityA));
77         startActivity(getContext(), activityA);
78         assertTopTaskActivity(activityA);
79 
80         ComponentName activityB = toComponentName(getTestContext(), ActivityB.class);
81         mService.registerActivityLaunchListener(new FilteredLaunchListener(activityB));
82         startActivity(getContext(), activityB);
83         assertTopTaskActivity(activityB);
84     }
85 
86     @Test
testActivityBlocking()87     public void testActivityBlocking() throws Exception {
88         ComponentName blackListedActivity = toComponentName(getTestContext(), ActivityC.class);
89         ComponentName blockingActivity = toComponentName(getTestContext(), BlockingActivity.class);
90         Intent blockingIntent = new Intent();
91         blockingIntent.setComponent(blockingActivity);
92 
93         // start a denylisted activity
94         mService.registerActivityLaunchListener(new FilteredLaunchListener(blackListedActivity));
95         startActivity(getContext(), blackListedActivity);
96         assertTopTaskActivity(blackListedActivity);
97 
98         // Instead of start activity, invoke blockActivity.
99         mService.registerActivityLaunchListener(new FilteredLaunchListener(blockingActivity));
100         mService.blockActivity(mTopTaskInfo[0], blockingIntent);
101         assertTopTaskActivity(blockingActivity);
102     }
103 
104     @Test
testRemovesFromTopTasks()105     public void testRemovesFromTopTasks() throws Exception {
106         ComponentName activityThatFinishesImmediately =
107                 toComponentName(getTestContext(), ActivityThatFinishesImmediately.class);
108         startActivity(getContext(), activityThatFinishesImmediately);
109         waitUntil(() -> topTasksHasComponent(activityThatFinishesImmediately));
110         waitUntil(() -> !topTasksHasComponent(activityThatFinishesImmediately));
111     }
112 
113     @Test
testGetTopTasksOnMultiDisplay()114     public void testGetTopTasksOnMultiDisplay() throws Exception {
115         String virtualDisplayName = "virtual_display";
116         DisplayManager displayManager = getContext().getSystemService(DisplayManager.class);
117         VirtualDisplay virtualDisplay = displayManager.createVirtualDisplay(
118                 virtualDisplayName, 10, 10, 10, null, 0);
119 
120         ComponentName activityA = toComponentName(getTestContext(), ActivityA.class);
121         startActivity(getContext(), activityA, Display.DEFAULT_DISPLAY);
122         waitUntil(() -> topTasksHasComponent(activityA));
123 
124         ComponentName activityB = toComponentName(getTestContext(), ActivityB.class);
125         startActivity(getContext(), activityB, virtualDisplay.getDisplay().getDisplayId());
126         waitUntil(() -> topTasksHasComponent(activityB));
127 
128         virtualDisplay.release();
129     }
130 
waitUntil(BooleanSupplier condition)131     private void waitUntil(BooleanSupplier condition) throws Exception {
132         while (!condition.getAsBoolean()) {
133             boolean didAquire =
134                     mActivityLaunchSemaphore.tryAcquire(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
135             if (!didAquire && !condition.getAsBoolean()) {
136                 throw new RuntimeException("failed while waiting for condition to become true");
137             }
138         }
139     }
140 
topTasksHasComponent(ComponentName component)141     private boolean topTasksHasComponent(ComponentName component) {
142         for (TopTaskInfoContainer topTaskInfoContainer : mService.getTopTasks()) {
143             if (topTaskInfoContainer.topActivity.equals(component)) {
144                 return true;
145             }
146         }
147         return false;
148     }
149 
150     /** Activity that closes itself after some timeout to clean up the screen. */
151     public static class TempActivity extends Activity {
152         @Override
onResume()153         protected void onResume() {
154             super.onResume();
155             getMainThreadHandler().postDelayed(this::finish, ACTIVITY_TIMEOUT_MS);
156         }
157     }
158 
159     public static class ActivityA extends TempActivity {}
160 
161     public static class ActivityB extends TempActivity {}
162 
163     public static class ActivityC extends TempActivity {}
164 
165     public static class ActivityThatFinishesImmediately extends Activity {
166 
167         @Override
onResume()168         protected void onResume() {
169             super.onResume();
170             finish();
171         }
172     }
173 
174     public static class BlockingActivity extends TempActivity {}
175 
assertTopTaskActivity(ComponentName activity)176     private void assertTopTaskActivity(ComponentName activity) throws Exception {
177         assertTrue(mActivityLaunchSemaphore.tryAcquire(DEFAULT_TIMEOUT_SECONDS, TimeUnit.SECONDS));
178         synchronized (mTopTaskInfo) {
179             assertEquals(activity, mTopTaskInfo[0].topActivity);
180         }
181     }
182 
getContext()183     private Context getContext() {
184         return InstrumentationRegistry.getTargetContext();
185     }
186 
getTestContext()187     private Context getTestContext() {
188         return InstrumentationRegistry.getContext();
189     }
190 
toComponentName(Context ctx, Class<?> cls)191     private static ComponentName toComponentName(Context ctx, Class<?> cls) {
192         return ComponentName.createRelative(ctx, cls.getName());
193     }
194 
startActivity(Context ctx, ComponentName name)195     private static void startActivity(Context ctx, ComponentName name) {
196         startActivity(ctx, name, Display.DEFAULT_DISPLAY);
197     }
198 
startActivity(Context ctx, ComponentName name, int displayId)199     private static void startActivity(Context ctx, ComponentName name, int displayId) {
200         Intent intent = new Intent();
201         intent.setComponent(name);
202         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
203 
204         ActivityOptions options = ActivityOptions.makeBasic();
205         options.setLaunchDisplayId(displayId);
206 
207         ctx.startActivity(intent, options.toBundle());
208     }
209 
210     private class FilteredLaunchListener
211             implements SystemActivityMonitoringService.ActivityLaunchListener {
212 
213         @Nullable
214         private final ComponentName mDesiredComponent;
215 
216         /**
217          * Creates an instance of an
218          * {@link com.android.car.SystemActivityMonitoringService.ActivityLaunchListener}
219          * that filters based on the component name or does not filter if component name is null.
220          */
FilteredLaunchListener(@ullable ComponentName desiredComponent)221         FilteredLaunchListener(@Nullable ComponentName desiredComponent) {
222             mDesiredComponent = desiredComponent;
223         }
224 
225         @Override
onActivityLaunch(TopTaskInfoContainer topTask)226         public void onActivityLaunch(TopTaskInfoContainer topTask) {
227             // Ignore activities outside of this test case
228             if (!getTestContext().getPackageName().equals(topTask.topActivity.getPackageName())) {
229                 Log.d(TAG, "Component launched from other package: "
230                         + topTask.topActivity.getClassName());
231                 return;
232             }
233             if (mDesiredComponent != null && !topTask.topActivity.equals(mDesiredComponent)) {
234                 Log.d(TAG, String.format("Unexpected component: %s. Expected: %s",
235                         topTask.topActivity.getClassName(), mDesiredComponent));
236                 return;
237             }
238 
239             synchronized (mTopTaskInfo) {
240                 mTopTaskInfo[0] = topTask;
241             }
242             mActivityLaunchSemaphore.release();
243         }
244     }
245 }
246