diff options
| author | 2020-04-24 04:47:55 +0000 | |
|---|---|---|
| committer | 2020-04-24 04:47:55 +0000 | |
| commit | bf3079a2ba245b7862a0cb8f5f95291ca49d9ace (patch) | |
| tree | 3fb42f1676e34e6fa8432f86dfab3828dc62adbc | |
| parent | cb16fcd464731d844410377aeef0aedc8c4c0adc (diff) | |
| parent | 16648affdda5c1f3d9eb950b90da2ecd1603b2c4 (diff) | |
Merge "Add side loaded listener and controller" into rvc-dev
| -rw-r--r-- | packages/CarSystemUI/res/values/config.xml | 1 | ||||
| -rw-r--r-- | packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java | 7 | ||||
| -rw-r--r-- | packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppController.java | 73 | ||||
| -rw-r--r-- | packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppDetector.java (renamed from packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java) | 6 | ||||
| -rw-r--r-- | packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppListener.java | 130 | ||||
| -rw-r--r-- | packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppStateController.java | 51 | ||||
| -rw-r--r-- | packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppDetectorTest.java (renamed from packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java) | 6 | ||||
| -rw-r--r-- | packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppListenerTest.java | 241 |
8 files changed, 509 insertions, 6 deletions
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml index eb1d9d0dd602..e8260034496c 100644 --- a/packages/CarSystemUI/res/values/config.xml +++ b/packages/CarSystemUI/res/values/config.xml @@ -111,5 +111,6 @@ <item>com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier</item> <item>com.android.systemui.car.window.SystemUIOverlayWindowManager</item> <item>com.android.systemui.car.volume.VolumeUI</item> + <item>com.android.systemui.car.sideloaded.SideLoadedAppController</item> </string-array> </resources> diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java index 58e4b9a81190..34afb132805c 100644 --- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java +++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java @@ -20,6 +20,7 @@ import com.android.systemui.biometrics.AuthController; import com.android.systemui.bubbles.dagger.BubbleModule; import com.android.systemui.car.navigationbar.CarNavigationBar; import com.android.systemui.car.notification.CarNotificationModule; +import com.android.systemui.car.sideloaded.SideLoadedAppController; import com.android.systemui.car.statusbar.CarStatusBar; import com.android.systemui.car.statusbar.CarStatusBarModule; import com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier; @@ -192,4 +193,10 @@ public abstract class CarSystemUIBinder { @IntoMap @ClassKey(SystemUIOverlayWindowManager.class) public abstract SystemUI bindSystemUIPrimaryWindowManager(SystemUIOverlayWindowManager sysui); + + /** Inject into SideLoadedAppController. */ + @Binds + @IntoMap + @ClassKey(SideLoadedAppController.class) + public abstract SystemUI bindSideLoadedAppController(SideLoadedAppController sysui); } diff --git a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppController.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppController.java new file mode 100644 index 000000000000..6b41b35f7625 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppController.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.car.sideloaded; + +import android.app.IActivityTaskManager; +import android.content.Context; +import android.os.RemoteException; +import android.util.Log; + +import com.android.systemui.SystemUI; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Controller responsible for detecting unsafe apps. + */ +@Singleton +public class SideLoadedAppController extends SystemUI { + private static final String TAG = SideLoadedAppController.class.getSimpleName(); + + private IActivityTaskManager mActivityTaskManager; + private SideLoadedAppListener mSideLoadedAppListener; + private SideLoadedAppDetector mSideLoadedAppDetector; + private SideLoadedAppStateController mSideLoadedAppStateController; + + @Inject + public SideLoadedAppController(Context context, + IActivityTaskManager activityTaskManager, + SideLoadedAppDetector sideLoadedAppDetector, + SideLoadedAppListener sideLoadedAppListener, + SideLoadedAppStateController sideLoadedAppStateController) { + super(context); + + mSideLoadedAppDetector = sideLoadedAppDetector; + mActivityTaskManager = activityTaskManager; + mSideLoadedAppListener = sideLoadedAppListener; + mSideLoadedAppStateController = sideLoadedAppStateController; + } + + @Override + public void start() { + } + + @Override + protected void onBootCompleted() { + Log.i(TAG, "OnBootCompleted"); + + try { + mActivityTaskManager.registerTaskStackListener(mSideLoadedAppListener); + } catch (RemoteException e) { + Log.e(TAG, "Could not register car side loaded app listener.", e); + } + + if (mSideLoadedAppDetector.hasUnsafeInstalledApps()) { + mSideLoadedAppStateController.onUnsafeInstalledAppsDetected(); + } + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppDetector.java index f145b148eaf7..5dcb9de4755e 100644 --- a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java +++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppDetector.java @@ -42,15 +42,15 @@ import javax.inject.Singleton; * An app is considered safe if is a system app or installed through whitelisted sources. */ @Singleton -public class CarSideLoadedAppDetector { - private static final String TAG = "CarSideLoadedDetector"; +public class SideLoadedAppDetector { + private static final String TAG = SideLoadedAppDetector.class.getSimpleName(); private final PackageManager mPackageManager; private final CarDeviceProvisionedController mCarDeviceProvisionedController; private final List<String> mAllowedAppInstallSources; @Inject - public CarSideLoadedAppDetector(@Main Resources resources, PackageManager packageManager, + public SideLoadedAppDetector(@Main Resources resources, PackageManager packageManager, CarDeviceProvisionedController deviceProvisionedController) { mAllowedAppInstallSources = Arrays.asList( resources.getStringArray(R.array.config_allowedAppInstallSources)); diff --git a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppListener.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppListener.java new file mode 100644 index 000000000000..c8c1a40b8032 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppListener.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.car.sideloaded; + +import android.app.ActivityManager; +import android.app.ActivityManager.StackInfo; +import android.app.IActivityTaskManager; +import android.app.TaskStackListener; +import android.content.ComponentName; +import android.hardware.display.DisplayManager; +import android.os.RemoteException; +import android.util.Log; +import android.view.Display; + +import java.util.List; + +import javax.inject.Inject; + +/** + * A TaskStackListener to detect when an unsafe app is launched/foregrounded. + */ +public class SideLoadedAppListener extends TaskStackListener { + private static final String TAG = SideLoadedAppListener.class.getSimpleName(); + + private IActivityTaskManager mActivityTaskManager; + private DisplayManager mDisplayManager; + private SideLoadedAppDetector mSideLoadedAppDetector; + private SideLoadedAppStateController mSideLoadedAppStateController; + + @Inject + SideLoadedAppListener(SideLoadedAppDetector sideLoadedAppDetector, + IActivityTaskManager activityTaskManager, + DisplayManager displayManager, + SideLoadedAppStateController sideLoadedAppStateController) { + mSideLoadedAppDetector = sideLoadedAppDetector; + mActivityTaskManager = activityTaskManager; + mDisplayManager = displayManager; + mSideLoadedAppStateController = sideLoadedAppStateController; + } + + @Override + public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException { + super.onTaskCreated(taskId, componentName); + + List<StackInfo> stackInfoList = mActivityTaskManager.getAllStackInfos(); + ActivityManager.StackInfo stackInfo = getStackInfo(stackInfoList, taskId); + if (stackInfo == null) { + Log.e(TAG, "Stack info was not available for taskId: " + taskId); + return; + } + + if (!mSideLoadedAppDetector.isSafe(stackInfo)) { + Display display = mDisplayManager.getDisplay(stackInfo.displayId); + mSideLoadedAppStateController.onUnsafeTaskCreatedOnDisplay(display); + } + } + + @Override + public void onTaskStackChanged() throws RemoteException { + super.onTaskStackChanged(); + + Display[] displays = mDisplayManager.getDisplays(); + for (Display display : displays) { + // Note that the stackInfoList is ordered by recency. + List<StackInfo> stackInfoList = + mActivityTaskManager.getAllStackInfosOnDisplay(display.getDisplayId()); + + if (stackInfoList == null) { + continue; + } + StackInfo stackInfo = getTopVisibleStackInfo(stackInfoList); + if (stackInfo == null) { + continue; + } + if (mSideLoadedAppDetector.isSafe(stackInfo)) { + mSideLoadedAppStateController.onSafeTaskDisplayedOnDisplay(display); + } else { + mSideLoadedAppStateController.onUnsafeTaskDisplayedOnDisplay(display); + } + } + } + + /** + * Returns stack info for a given taskId. + */ + private ActivityManager.StackInfo getStackInfo( + List<ActivityManager.StackInfo> stackInfoList, int taskId) { + if (stackInfoList == null) { + return null; + } + for (ActivityManager.StackInfo stackInfo : stackInfoList) { + if (stackInfo.taskIds == null) { + continue; + } + for (int stackTaskId : stackInfo.taskIds) { + if (taskId == stackTaskId) { + return stackInfo; + } + } + } + return null; + } + + /** + * Returns the first visible stackInfo. + */ + private ActivityManager.StackInfo getTopVisibleStackInfo( + List<ActivityManager.StackInfo> stackInfoList) { + for (ActivityManager.StackInfo stackInfo : stackInfoList) { + if (stackInfo.visible) { + return stackInfo; + } + } + return null; + } +} diff --git a/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppStateController.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppStateController.java new file mode 100644 index 000000000000..1d66ddaf7aa9 --- /dev/null +++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppStateController.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.car.sideloaded; + +import android.util.Log; +import android.view.Display; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Manager responsible for displaying proper UI when an unsafe app is detected. + */ +@Singleton +public class SideLoadedAppStateController { + private static final String TAG = SideLoadedAppStateController.class.getSimpleName(); + + @Inject + SideLoadedAppStateController() { + } + + void onUnsafeInstalledAppsDetected() { + Log.d(TAG, "Unsafe installed apps detected."); + } + + void onUnsafeTaskCreatedOnDisplay(Display display) { + Log.d(TAG, "Unsafe task created on display " + display.getDisplayId() + "."); + } + + void onSafeTaskDisplayedOnDisplay(Display display) { + Log.d(TAG, "Safe task displayed on display " + display.getDisplayId() + "."); + } + + void onUnsafeTaskDisplayedOnDisplay(Display display) { + Log.d(TAG, "Unsafe task displayed on display " + display.getDisplayId() + "."); + } +} diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppDetectorTest.java index 80f3d1ee5dec..77620f3fb345 100644 --- a/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppDetectorTest.java @@ -47,14 +47,14 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @SmallTest -public class CarSideLoadedAppDetectorTest extends SysuiTestCase { +public class SideLoadedAppDetectorTest extends SysuiTestCase { private static final String SAFE_VENDOR = "com.safe.vendor"; private static final String UNSAFE_VENDOR = "com.unsafe.vendor"; private static final String APP_PACKAGE_NAME = "com.test"; private static final String APP_CLASS_NAME = ".TestClass"; - private CarSideLoadedAppDetector mSideLoadedAppDetector; + private SideLoadedAppDetector mSideLoadedAppDetector; @Mock private PackageManager mPackageManager; @@ -70,7 +70,7 @@ public class CarSideLoadedAppDetectorTest extends SysuiTestCase { testableResources.addOverride(R.array.config_allowedAppInstallSources, allowedAppInstallSources); - mSideLoadedAppDetector = new CarSideLoadedAppDetector(testableResources.getResources(), + mSideLoadedAppDetector = new SideLoadedAppDetector(testableResources.getResources(), mPackageManager, mCarDeviceProvisionedController); } diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppListenerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppListenerTest.java new file mode 100644 index 000000000000..73f9f6a55afc --- /dev/null +++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/SideLoadedAppListenerTest.java @@ -0,0 +1,241 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.car.sideloaded; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager.StackInfo; +import android.app.IActivityTaskManager; +import android.content.ComponentName; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManagerGlobal; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.Display; +import android.view.DisplayAdjustments; +import android.view.DisplayInfo; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +@SmallTest +public class SideLoadedAppListenerTest extends SysuiTestCase { + + private static final String APP_PACKAGE_NAME = "com.test"; + private static final String APP_CLASS_NAME = ".TestClass"; + + private SideLoadedAppListener mSideLoadedAppListener; + + @Mock + private SideLoadedAppDetector mSideLoadedAppDetector; + @Mock + private DisplayManager mDisplayManager; + @Mock + private IActivityTaskManager mActivityTaskManager; + @Mock + private SideLoadedAppStateController mSideLoadedAppStateController; + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + + mSideLoadedAppListener = new SideLoadedAppListener(mSideLoadedAppDetector, + mActivityTaskManager, mDisplayManager, mSideLoadedAppStateController); + } + + @Test + public void onTaskCreated_safeTask_callsNoMethods() throws Exception { + int taskId = 999; + int displayId = 123; + ComponentName componentName = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); + + StackInfo stackInfo1 = createTask(1, /* isVisible= */ true); + stackInfo1.taskIds = new int[] { 11, 22, 33 }; + + StackInfo stackInfo2 = createTask(2, /* isVisible= */ true); + stackInfo2.taskIds = new int[] { 111, 222, 333, taskId }; + stackInfo2.displayId = displayId; + + List<StackInfo> stackInfoList = Arrays.asList(stackInfo1, stackInfo2); + + when(mActivityTaskManager.getAllStackInfos()).thenReturn(stackInfoList); + when(mSideLoadedAppDetector.isSafe(stackInfo2)).thenReturn(true); + + mSideLoadedAppListener.onTaskCreated(taskId, componentName); + + verify(mSideLoadedAppDetector, never()).isSafe(stackInfo1); + verify(mSideLoadedAppDetector).isSafe(stackInfo2); + + verify(mSideLoadedAppStateController, never()).onUnsafeTaskCreatedOnDisplay(any()); + verify(mSideLoadedAppStateController, never()).onSafeTaskDisplayedOnDisplay(any()); + verify(mSideLoadedAppStateController, never()).onUnsafeTaskDisplayedOnDisplay(any()); + } + + @Test + public void onTaskCreated_unsafeTask_callsUnsafeTaskCreated() throws Exception { + int taskId = 999; + int displayId = 123; + ComponentName componentName = new ComponentName(APP_PACKAGE_NAME, APP_CLASS_NAME); + + StackInfo stackInfo1 = createTask(1, /* isVisible= */ true); + stackInfo1.taskIds = new int[] { 11, 22, 33 }; + StackInfo stackInfo2 = createTask(2, /* isVisible= */ true); + stackInfo2.taskIds = new int[] { 111, 222, 333, taskId }; + stackInfo2.displayId = displayId; + List<StackInfo> stackInfoList = Arrays.asList(stackInfo1, stackInfo2); + + Display display = createDisplay(displayId); + + when(mActivityTaskManager.getAllStackInfos()).thenReturn(stackInfoList); + when(mSideLoadedAppDetector.isSafe(stackInfo2)).thenReturn(false); + when(mDisplayManager.getDisplay(displayId)).thenReturn(display); + + mSideLoadedAppListener.onTaskCreated(taskId, componentName); + + verify(mSideLoadedAppDetector, never()).isSafe(stackInfo1); + verify(mSideLoadedAppDetector).isSafe(stackInfo2); + + verify(mSideLoadedAppStateController).onUnsafeTaskCreatedOnDisplay(display); + verify(mSideLoadedAppStateController, never()).onSafeTaskDisplayedOnDisplay(any()); + verify(mSideLoadedAppStateController, never()).onUnsafeTaskDisplayedOnDisplay(any()); + } + + @Test + public void onTaskStackChanged_safeTask_callsSafeTaskDisplayed() throws Exception { + Display display = createDisplay(123); + StackInfo stackInfo1 = createTask(1, /* isVisible= */ false); + StackInfo stackInfo2 = createTask(2, /* isVisible= */ true); + StackInfo stackInfo3 = createTask(3, /* isVisible= */ true); + List<StackInfo> stackInfoList = Arrays.asList(stackInfo1, stackInfo2, stackInfo3); + + when(mActivityTaskManager.getAllStackInfosOnDisplay(display.getDisplayId())) + .thenReturn(stackInfoList); + when(mSideLoadedAppDetector.isSafe(stackInfo2)).thenReturn(true); + when(mDisplayManager.getDisplays()).thenReturn(new Display[] { display }); + + mSideLoadedAppListener.onTaskStackChanged(); + + verify(mSideLoadedAppDetector, never()).isSafe(stackInfo1); + verify(mSideLoadedAppDetector).isSafe(stackInfo2); + verify(mSideLoadedAppDetector, never()).isSafe(stackInfo3); + + verify(mSideLoadedAppStateController, never()).onUnsafeTaskCreatedOnDisplay(any()); + verify(mSideLoadedAppStateController).onSafeTaskDisplayedOnDisplay(display); + verify(mSideLoadedAppStateController, never()).onUnsafeTaskDisplayedOnDisplay(any()); + } + + @Test + public void onTaskStackChanged_unsafeTask_callsUnsafeTaskDisplayed() throws Exception { + Display display = createDisplay(123); + StackInfo stackInfo1 = createTask(1, /* isVisible= */ false); + StackInfo stackInfo2 = createTask(2, /* isVisible= */ true); + StackInfo stackInfo3 = createTask(3, /* isVisible= */ true); + List<StackInfo> stackInfoList = Arrays.asList(stackInfo1, stackInfo2, stackInfo3); + + when(mActivityTaskManager.getAllStackInfosOnDisplay(display.getDisplayId())) + .thenReturn(stackInfoList); + when(mSideLoadedAppDetector.isSafe(stackInfo2)).thenReturn(false); + when(mDisplayManager.getDisplays()).thenReturn(new Display[] { display }); + + mSideLoadedAppListener.onTaskStackChanged(); + + verify(mSideLoadedAppDetector, never()).isSafe(stackInfo1); + verify(mSideLoadedAppDetector).isSafe(stackInfo2); + verify(mSideLoadedAppDetector, never()).isSafe(stackInfo3); + + verify(mSideLoadedAppStateController, never()).onUnsafeTaskCreatedOnDisplay(any()); + verify(mSideLoadedAppStateController, never()).onSafeTaskDisplayedOnDisplay(any()); + verify(mSideLoadedAppStateController).onUnsafeTaskDisplayedOnDisplay(display); + } + + @Test + public void onTaskStackChanged_multiDisplay_callsTasksDisplayed() throws Exception { + Display display1 = createDisplay(1); + StackInfo stackInfo1 = createTask(1, /* isVisible= */ false); + StackInfo stackInfo2 = createTask(2, /* isVisible= */ true); + StackInfo stackInfo3 = createTask(3, /* isVisible= */ true); + List<StackInfo> display1Stack = Arrays.asList(stackInfo1, stackInfo2, stackInfo3); + + Display display2 = createDisplay(2); + StackInfo stackInfo4 = createTask(4, /* isVisible= */ true); + List<StackInfo> display2Stack = Collections.singletonList(stackInfo4); + + Display display3 = createDisplay(3); + StackInfo stackInfo5 = createTask(5, /* isVisible= */ true); + List<StackInfo> display3Stack = Collections.singletonList(stackInfo5); + + when(mActivityTaskManager.getAllStackInfosOnDisplay(display1.getDisplayId())) + .thenReturn(display1Stack); + when(mActivityTaskManager.getAllStackInfosOnDisplay(display2.getDisplayId())) + .thenReturn(display2Stack); + when(mActivityTaskManager.getAllStackInfosOnDisplay(display3.getDisplayId())) + .thenReturn(display3Stack); + + when(mSideLoadedAppDetector.isSafe(stackInfo2)).thenReturn(true); + when(mSideLoadedAppDetector.isSafe(stackInfo4)).thenReturn(false); + when(mSideLoadedAppDetector.isSafe(stackInfo5)).thenReturn(true); + + when(mDisplayManager.getDisplays()) + .thenReturn(new Display[] { display1, display2, display3}); + + mSideLoadedAppListener.onTaskStackChanged(); + + verify(mSideLoadedAppDetector, never()).isSafe(stackInfo1); + verify(mSideLoadedAppDetector).isSafe(stackInfo2); + verify(mSideLoadedAppDetector, never()).isSafe(stackInfo3); + verify(mSideLoadedAppDetector).isSafe(stackInfo4); + verify(mSideLoadedAppDetector).isSafe(stackInfo5); + + verify(mSideLoadedAppStateController, never()).onUnsafeTaskCreatedOnDisplay(any()); + verify(mSideLoadedAppStateController).onSafeTaskDisplayedOnDisplay(display1); + verify(mSideLoadedAppStateController, never()).onUnsafeTaskDisplayedOnDisplay(display2); + verify(mSideLoadedAppStateController).onSafeTaskDisplayedOnDisplay(display3); + verify(mSideLoadedAppStateController, never()).onUnsafeTaskDisplayedOnDisplay(display1); + verify(mSideLoadedAppStateController).onUnsafeTaskDisplayedOnDisplay(display2); + verify(mSideLoadedAppStateController, never()).onUnsafeTaskDisplayedOnDisplay(display3); + } + + private Display createDisplay(int id) { + return new Display(DisplayManagerGlobal.getInstance(), + id, + new DisplayInfo(), + DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); + } + + private StackInfo createTask(int id, boolean isVisible) { + StackInfo stackInfo = new StackInfo(); + stackInfo.stackId = id; + stackInfo.visible = isVisible; + return stackInfo; + } +} |