summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
author Wei Sheng Shih <wilsonshih@google.com> 2020-11-11 02:30:02 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2020-11-11 02:30:02 +0000
commit079f596c4ea9ac47d940c40a24ef293639fa408f (patch)
tree2b423cb450ffb06f6d328a38a7b7198cf9430f01 /libs
parenta0131174f933c0c48452b925bc3e2bf6ea0a1eb2 (diff)
parentc1c4c5a6612fc3134f1ce1233e5851037719c491 (diff)
Merge "Delegate splash screen starting window to SystemUI(1/N)"
Diffstat (limited to 'libs')
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java23
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java343
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java147
4 files changed, 514 insertions, 4 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 2d20feeb0832..ece063cabf7c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -27,6 +27,7 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG
import android.annotation.IntDef;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.WindowConfiguration.WindowingMode;
+import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.util.ArrayMap;
@@ -44,6 +45,7 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -104,21 +106,26 @@ public class ShellTaskOrganizer extends TaskOrganizer {
private final Transitions mTransitions;
private final Object mLock = new Object();
+ private final StartingSurfaceDrawer mStartingSurfaceDrawer;
public ShellTaskOrganizer(SyncTransactionQueue syncQueue, TransactionPool transactionPool,
- ShellExecutor mainExecutor, ShellExecutor animExecutor) {
- this(null, syncQueue, transactionPool, mainExecutor, animExecutor);
+ ShellExecutor mainExecutor, ShellExecutor animExecutor, Context context) {
+ this(null, syncQueue, transactionPool, mainExecutor, animExecutor, context);
}
@VisibleForTesting
ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
SyncTransactionQueue syncQueue, TransactionPool transactionPool,
- ShellExecutor mainExecutor, ShellExecutor animExecutor) {
+ ShellExecutor mainExecutor, ShellExecutor animExecutor, Context context) {
super(taskOrganizerController, mainExecutor);
addListenerForType(new FullscreenTaskListener(syncQueue), TASK_LISTENER_TYPE_FULLSCREEN);
addListenerForType(new LetterboxTaskListener(syncQueue), TASK_LISTENER_TYPE_LETTERBOX);
mTransitions = new Transitions(this, transactionPool, mainExecutor, animExecutor);
if (Transitions.ENABLE_SHELL_TRANSITIONS) registerTransitionPlayer(mTransitions);
+ // TODO(b/131727939) temporarily live here, the starting surface drawer should be controlled
+ // by a controller, that class should be create while porting
+ // ActivityRecord#addStartingWindow to WMShell.
+ mStartingSurfaceDrawer = new StartingSurfaceDrawer(context);
}
@Override
@@ -235,6 +242,16 @@ public class ShellTaskOrganizer extends TaskOrganizer {
}
@Override
+ public void addStartingWindow(RunningTaskInfo taskInfo, IBinder appToken) {
+ mStartingSurfaceDrawer.addStartingWindow(taskInfo, appToken);
+ }
+
+ @Override
+ public void removeStartingWindow(RunningTaskInfo taskInfo) {
+ mStartingSurfaceDrawer.removeStartingWindow(taskInfo);
+ }
+
+ @Override
public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
synchronized (mLock) {
onTaskAppeared(new TaskAppearedInfo(taskInfo, leash));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
new file mode 100644
index 000000000000..ee79824ff4a7
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -0,0 +1,343 @@
+/*
+ * 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.wm.shell.startingsurface;
+
+import static android.content.Context.CONTEXT_RESTRICTED;
+import static android.content.res.Configuration.EMPTY;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.hardware.display.DisplayManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.window.TaskOrganizer;
+
+import com.android.internal.R;
+import com.android.internal.policy.PhoneWindow;
+
+import java.util.function.Consumer;
+
+/**
+ * Implementation to draw the starting window to an application, and remove the starting window
+ * until the application displays its own window.
+ *
+ * When receive {@link TaskOrganizer#addStartingWindow} callback, use this class to create a
+ * starting window and attached to the Task, then when the Task want to remove the starting window,
+ * the TaskOrganizer will receive {@link TaskOrganizer#removeStartingWindow} callback then use this
+ * class to remove the starting window of the Task.
+ * @hide
+ */
+public class StartingSurfaceDrawer {
+ private static final String TAG = StartingSurfaceDrawer.class.getSimpleName();
+ private static final boolean DEBUG_SPLASH_SCREEN = false;
+
+ private final Context mContext;
+ private final DisplayManager mDisplayManager;
+
+ // TODO(b/131727939) remove this when clearing ActivityRecord
+ private static final int REMOVE_WHEN_TIMEOUT = 2000;
+
+ public StartingSurfaceDrawer(Context context) {
+ mContext = context;
+ mDisplayManager = mContext.getSystemService(DisplayManager.class);
+ }
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+ private final SparseArray<TaskScreenView> mTaskScreenViews = new SparseArray<>();
+
+ /** Obtain proper context for showing splash screen on the provided display. */
+ private Context getDisplayContext(Context context, int displayId) {
+ if (displayId == DEFAULT_DISPLAY) {
+ // The default context fits.
+ return context;
+ }
+
+ final Display targetDisplay = mDisplayManager.getDisplay(displayId);
+ if (targetDisplay == null) {
+ // Failed to obtain the non-default display where splash screen should be shown,
+ // lets not show at all.
+ return null;
+ }
+
+ return context.createDisplayContext(targetDisplay);
+ }
+
+ /**
+ * Called when a task need a starting window.
+ */
+ public void addStartingWindow(ActivityManager.RunningTaskInfo taskInfo, IBinder appToken) {
+
+ final ActivityInfo activityInfo = taskInfo.topActivityInfo;
+ final int displayId = taskInfo.displayId;
+ if (activityInfo.packageName == null) {
+ return;
+ }
+
+ CharSequence nonLocalizedLabel = activityInfo.nonLocalizedLabel;
+ int labelRes = activityInfo.labelRes;
+ if (activityInfo.nonLocalizedLabel == null && activityInfo.labelRes == 0) {
+ ApplicationInfo app = activityInfo.applicationInfo;
+ nonLocalizedLabel = app.nonLocalizedLabel;
+ labelRes = app.labelRes;
+ }
+
+ Context context = mContext;
+ final int theme = activityInfo.getThemeResource();
+ if (DEBUG_SPLASH_SCREEN) {
+ Slog.d(TAG, "addSplashScreen " + activityInfo.packageName
+ + ": nonLocalizedLabel=" + nonLocalizedLabel + " theme="
+ + Integer.toHexString(theme) + " task= " + taskInfo.taskId);
+ }
+
+ // Obtain proper context to launch on the right display.
+ final Context displayContext = getDisplayContext(context, displayId);
+ if (displayContext == null) {
+ // Can't show splash screen on requested display, so skip showing at all.
+ return;
+ }
+ context = displayContext;
+
+ if (theme != context.getThemeResId() || labelRes != 0) {
+ try {
+ context = context.createPackageContext(
+ activityInfo.packageName, CONTEXT_RESTRICTED);
+ context.setTheme(theme);
+ } catch (PackageManager.NameNotFoundException e) {
+ // Ignore
+ }
+ }
+
+ final Configuration taskConfig = taskInfo.getConfiguration();
+ if (taskConfig != null && !taskConfig.equals(EMPTY)) {
+ if (DEBUG_SPLASH_SCREEN) {
+ Slog.d(TAG, "addSplashScreen: creating context based"
+ + " on task Configuration " + taskConfig + " for splash screen");
+ }
+ final Context overrideContext = context.createConfigurationContext(taskConfig);
+ overrideContext.setTheme(theme);
+ final TypedArray typedArray = overrideContext.obtainStyledAttributes(
+ com.android.internal.R.styleable.Window);
+ final int resId = typedArray.getResourceId(R.styleable.Window_windowBackground, 0);
+ if (resId != 0 && overrideContext.getDrawable(resId) != null) {
+ // We want to use the windowBackground for the override context if it is
+ // available, otherwise we use the default one to make sure a themed starting
+ // window is displayed for the app.
+ if (DEBUG_SPLASH_SCREEN) {
+ Slog.d(TAG, "addSplashScreen: apply overrideConfig"
+ + taskConfig + " to starting window resId=" + resId);
+ }
+ context = overrideContext;
+ }
+ typedArray.recycle();
+ }
+
+ int windowFlags = 0;
+ if ((activityInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
+ windowFlags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
+ }
+
+ final boolean[] showWallpaper = new boolean[1];
+ final int[] splashscreenContentResId = new int[1];
+ getWindowResFromContext(context, a -> {
+ splashscreenContentResId[0] =
+ a.getResourceId(R.styleable.Window_windowSplashscreenContent, 0);
+ showWallpaper[0] = a.getBoolean(R.styleable.Window_windowShowWallpaper, false);
+ });
+ if (showWallpaper[0]) {
+ windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+ }
+
+ final PhoneWindow win = new PhoneWindow(context);
+ win.setIsStartingWindow(true);
+
+ CharSequence label = context.getResources().getText(labelRes, null);
+ // Only change the accessibility title if the label is localized
+ if (label != null) {
+ win.setTitle(label, true);
+ } else {
+ win.setTitle(nonLocalizedLabel, false);
+ }
+
+ win.setType(WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
+
+ // Assumes it's safe to show starting windows of launched apps while
+ // the keyguard is being hidden. This is okay because starting windows never show
+ // secret information.
+ // TODO(b/113840485): Occluded may not only happen on default display
+ if (displayId == DEFAULT_DISPLAY) {
+ windowFlags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+ }
+
+ // Force the window flags: this is a fake window, so it is not really
+ // touchable or focusable by the user. We also add in the ALT_FOCUSABLE_IM
+ // flag because we do know that the next window will take input
+ // focus, so we want to get the IME window up on top of us right away.
+ win.setFlags(windowFlags
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
+ windowFlags
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
+
+ final int iconRes = activityInfo.getIconResource();
+ final int logoRes = activityInfo.getLogoResource();
+ win.setDefaultIcon(iconRes);
+ win.setDefaultLogo(logoRes);
+
+ win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT);
+
+ final WindowManager.LayoutParams params = win.getAttributes();
+ params.token = appToken;
+ params.packageName = activityInfo.packageName;
+ params.windowAnimations = win.getWindowStyle().getResourceId(
+ com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
+ params.privateFlags |=
+ WindowManager.LayoutParams.PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED;
+ params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+ // Setting as trusted overlay to let touches pass through. This is safe because this
+ // window is controlled by the system.
+ params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+
+ final Resources res = context.getResources();
+ final boolean supportsScreen = res != null && (res.getCompatibilityInfo() != null
+ && res.getCompatibilityInfo().supportsScreen());
+ if (!supportsScreen) {
+ params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
+ }
+
+ params.setTitle("Splash Screen " + activityInfo.packageName);
+ addSplashscreenContent(win, context, splashscreenContentResId[0]);
+
+ final View view = win.getDecorView();
+
+ if (DEBUG_SPLASH_SCREEN) {
+ Slog.d(TAG, "Adding splash screen window for "
+ + activityInfo.packageName + " / " + appToken + ": " + view);
+ }
+ final WindowManager wm = context.getSystemService(WindowManager.class);
+ postAddWindow(taskInfo.taskId, appToken, view, wm, params);
+ }
+
+ /**
+ * Called when the content of a task is ready to show, starting window can be removed.
+ */
+ public void removeStartingWindow(ActivityManager.RunningTaskInfo taskInfo) {
+ if (DEBUG_SPLASH_SCREEN) {
+ Slog.d(TAG, "Task start finish, remove starting surface for task " + taskInfo.taskId);
+ }
+ mHandler.post(() -> removeWindowSynced(taskInfo.taskId));
+ }
+
+ protected void postAddWindow(int taskId, IBinder appToken,
+ View view, WindowManager wm, WindowManager.LayoutParams params) {
+ mHandler.post(() -> {
+ boolean shouldSaveView = true;
+ try {
+ wm.addView(view, params);
+ } catch (WindowManager.BadTokenException e) {
+ // ignore
+ Slog.w(TAG, appToken + " already running, starting window not displayed. "
+ + e.getMessage());
+ shouldSaveView = false;
+ } catch (RuntimeException e) {
+ // don't crash if something else bad happens, for example a
+ // failure loading resources because we are loading from an app
+ // on external storage that has been unmounted.
+ Slog.w(TAG, appToken + " failed creating starting window", e);
+ shouldSaveView = false;
+ } finally {
+ if (view != null && view.getParent() == null) {
+ Slog.w(TAG, "view not successfully added to wm, removing view");
+ wm.removeViewImmediate(view);
+ shouldSaveView = false;
+ }
+ }
+
+ if (shouldSaveView) {
+ removeWindowSynced(taskId);
+ mHandler.postDelayed(() -> removeWindowSynced(taskId), REMOVE_WHEN_TIMEOUT);
+ final TaskScreenView tView = new TaskScreenView(view);
+ mTaskScreenViews.put(taskId, tView);
+ }
+ });
+ }
+
+ protected void removeWindowSynced(int taskId) {
+ final TaskScreenView preView = mTaskScreenViews.get(taskId);
+ if (preView != null) {
+ if (preView.mDecorView != null) {
+ if (DEBUG_SPLASH_SCREEN) {
+ Slog.v(TAG, "Removing splash screen window for task: " + taskId);
+ }
+ final WindowManager wm = preView.mDecorView.getContext()
+ .getSystemService(WindowManager.class);
+ wm.removeView(preView.mDecorView);
+ }
+ mTaskScreenViews.remove(taskId);
+ }
+ }
+
+ private void getWindowResFromContext(Context ctx, Consumer<TypedArray> consumer) {
+ final TypedArray a = ctx.obtainStyledAttributes(R.styleable.Window);
+ consumer.accept(a);
+ a.recycle();
+ }
+
+ /**
+ * Record the views in a starting window.
+ */
+ private static class TaskScreenView {
+ private final View mDecorView;
+
+ TaskScreenView(View decorView) {
+ mDecorView = decorView;
+ }
+ }
+
+ private void addSplashscreenContent(PhoneWindow win, Context ctx,
+ int splashscreenContentResId) {
+ if (splashscreenContentResId == 0) {
+ return;
+ }
+ final Drawable drawable = ctx.getDrawable(splashscreenContentResId);
+ if (drawable == null) {
+ return;
+ }
+
+ // We wrap this into a view so the system insets get applied to the drawable.
+ final View v = new View(ctx);
+ v.setBackground(drawable);
+ win.setContentView(v);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index e4155a257cee..fdf4d31f0281 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -36,6 +36,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.graphics.Rect;
import android.os.Binder;
@@ -71,6 +72,8 @@ public class ShellTaskOrganizerTests {
@Mock
private ITaskOrganizerController mTaskOrganizerController;
+ @Mock
+ private Context mContext;
ShellTaskOrganizer mOrganizer;
private final SyncTransactionQueue mSyncTransactionQueue = mock(SyncTransactionQueue.class);
@@ -106,7 +109,7 @@ public class ShellTaskOrganizerTests {
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mSyncTransactionQueue,
- mTransactionPool, mTestExecutor, mTestExecutor));
+ mTransactionPool, mTestExecutor, mTestExecutor, mContext));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
new file mode 100644
index 000000000000..f5628abb100f
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -0,0 +1,147 @@
+/*
+ * 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 unittest.src.com.android.wm.shell.startingsurface;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.testing.TestableContext;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.startingsurface.StartingSurfaceDrawer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Tests for the starting surface drawer.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class StartingSurfaceDrawerTests {
+ @Mock
+ private IBinder mBinder;
+ @Mock
+ private WindowManager mMockWindowManager;
+
+ TestStartingSurfaceDrawer mStartingSurfaceDrawer;
+
+ static final class TestStartingSurfaceDrawer extends StartingSurfaceDrawer{
+ int mAddWindowForTask = 0;
+
+ TestStartingSurfaceDrawer(Context context) {
+ super(context);
+ }
+
+ @Override
+ protected void postAddWindow(int taskId, IBinder appToken,
+ View view, WindowManager wm, WindowManager.LayoutParams params) {
+ // listen for addView
+ mAddWindowForTask = taskId;
+ }
+
+ @Override
+ protected void removeWindowSynced(int taskId) {
+ // listen for removeView
+ if (mAddWindowForTask == taskId) {
+ mAddWindowForTask = 0;
+ }
+ }
+ }
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ final TestableContext context = new TestableContext(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
+ final WindowManager realWindowManager = context.getSystemService(WindowManager.class);
+ final WindowMetrics metrics = realWindowManager.getMaximumWindowMetrics();
+ context.addMockSystemService(WindowManager.class, mMockWindowManager);
+
+ spyOn(context);
+ spyOn(realWindowManager);
+ try {
+ doReturn(context).when(context).createPackageContext(anyString(), anyInt());
+ } catch (PackageManager.NameNotFoundException e) {
+ //
+ }
+ doReturn(metrics).when(mMockWindowManager).getMaximumWindowMetrics();
+ doNothing().when(mMockWindowManager).addView(any(), any());
+
+ mStartingSurfaceDrawer = spy(new TestStartingSurfaceDrawer(context));
+ }
+
+ @Test
+ public void testAddSplashScreenSurface() {
+ final int taskId = 1;
+ final Handler mainLoop = new Handler(Looper.getMainLooper());
+ final ActivityManager.RunningTaskInfo taskInfo =
+ createTaskInfo(taskId, WINDOWING_MODE_FULLSCREEN);
+ mStartingSurfaceDrawer.addStartingWindow(taskInfo, mBinder);
+ waitHandlerIdle(mainLoop);
+ verify(mStartingSurfaceDrawer).postAddWindow(eq(taskId), eq(mBinder), any(), any(), any());
+ assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, taskId);
+
+ mStartingSurfaceDrawer.removeStartingWindow(taskInfo);
+ waitHandlerIdle(mainLoop);
+ verify(mStartingSurfaceDrawer).removeWindowSynced(eq(taskId));
+ assertEquals(mStartingSurfaceDrawer.mAddWindowForTask, 0);
+ }
+
+ private ActivityManager.RunningTaskInfo createTaskInfo(int taskId, int windowingMode) {
+ ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
+ final ActivityInfo info = new ActivityInfo();
+ info.applicationInfo = new ApplicationInfo();
+ info.packageName = "test";
+ info.theme = android.R.style.Theme;
+ taskInfo.topActivityInfo = info;
+ taskInfo.taskId = taskId;
+ taskInfo.configuration.windowConfiguration.setWindowingMode(windowingMode);
+ return taskInfo;
+ }
+
+ private static void waitHandlerIdle(Handler handler) {
+ handler.runWithScissors(() -> { }, 0 /* timeout */);
+ }
+}