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