summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Babak Bostan <babakbo@google.com> 2020-04-24 04:47:55 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2020-04-24 04:47:55 +0000
commitbf3079a2ba245b7862a0cb8f5f95291ca49d9ace (patch)
tree3fb42f1676e34e6fa8432f86dfab3828dc62adbc
parentcb16fcd464731d844410377aeef0aedc8c4c0adc (diff)
parent16648affdda5c1f3d9eb950b90da2ecd1603b2c4 (diff)
Merge "Add side loaded listener and controller" into rvc-dev
-rw-r--r--packages/CarSystemUI/res/values/config.xml1
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java7
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppController.java73
-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.java130
-rw-r--r--packages/CarSystemUI/src/com/android/systemui/car/sideloaded/SideLoadedAppStateController.java51
-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.java241
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;
+ }
+}