| /* |
| * 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.os.Trace.TRACE_TAG_WINDOW_MANAGER; |
| import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN; |
| import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_NONE; |
| import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SNAPSHOT; |
| import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN; |
| import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN; |
| import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS; |
| |
| import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; |
| import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW; |
| |
| import android.app.ActivityManager.RunningTaskInfo; |
| import android.app.TaskInfo; |
| import android.content.Context; |
| import android.graphics.Color; |
| import android.os.Trace; |
| import android.util.SparseIntArray; |
| import android.window.StartingWindowInfo; |
| import android.window.StartingWindowInfo.StartingWindowType; |
| import android.window.StartingWindowRemovalInfo; |
| import android.window.TaskOrganizer; |
| import android.window.TaskSnapshot; |
| |
| import androidx.annotation.BinderThread; |
| import androidx.annotation.VisibleForTesting; |
| |
| import com.android.internal.annotations.GuardedBy; |
| import com.android.internal.util.function.TriConsumer; |
| import com.android.launcher3.icons.IconProvider; |
| import com.android.wm.shell.ShellTaskOrganizer; |
| import com.android.wm.shell.common.ExternalInterfaceBinder; |
| import com.android.wm.shell.common.RemoteCallable; |
| import com.android.wm.shell.common.ShellExecutor; |
| import com.android.wm.shell.common.SingleInstanceRemoteListener; |
| import com.android.wm.shell.common.TransactionPool; |
| import com.android.wm.shell.sysui.ShellController; |
| import com.android.wm.shell.sysui.ShellInit; |
| |
| /** |
| * 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. |
| * Besides add/remove starting window, There is an API #setStartingWindowListener to register |
| * a callback when starting window is about to create which let the registerer knows the next |
| * starting window's type. |
| * So far all classes in this package is an enclose system so there is no interact with other shell |
| * component, all the methods must be executed in splash screen thread or the thread used in |
| * constructor to keep everything synchronized. |
| * @hide |
| */ |
| public class StartingWindowController implements RemoteCallable<StartingWindowController> { |
| public static final String TAG = "ShellStartingWindow"; |
| |
| private static final long TASK_BG_COLOR_RETAIN_TIME_MS = 5000; |
| |
| private final StartingSurfaceDrawer mStartingSurfaceDrawer; |
| private final StartingWindowTypeAlgorithm mStartingWindowTypeAlgorithm; |
| |
| private TriConsumer<Integer, Integer, Integer> mTaskLaunchingCallback; |
| private final StartingSurfaceImpl mImpl = new StartingSurfaceImpl(); |
| private final Context mContext; |
| private final ShellController mShellController; |
| private final ShellTaskOrganizer mShellTaskOrganizer; |
| private final ShellExecutor mSplashScreenExecutor; |
| /** |
| * Need guarded because it has exposed to StartingSurface |
| */ |
| @GuardedBy("mTaskBackgroundColors") |
| private final SparseIntArray mTaskBackgroundColors = new SparseIntArray(); |
| |
| public StartingWindowController(Context context, |
| ShellInit shellInit, |
| ShellController shellController, |
| ShellTaskOrganizer shellTaskOrganizer, |
| ShellExecutor splashScreenExecutor, |
| StartingWindowTypeAlgorithm startingWindowTypeAlgorithm, |
| IconProvider iconProvider, |
| TransactionPool pool) { |
| mContext = context; |
| mShellController = shellController; |
| mShellTaskOrganizer = shellTaskOrganizer; |
| mStartingSurfaceDrawer = new StartingSurfaceDrawer(context, splashScreenExecutor, |
| iconProvider, pool); |
| mStartingWindowTypeAlgorithm = startingWindowTypeAlgorithm; |
| mSplashScreenExecutor = splashScreenExecutor; |
| shellInit.addInitCallback(this::onInit, this); |
| } |
| |
| /** |
| * Provide the implementation for Shell Module. |
| */ |
| public StartingSurface asStartingSurface() { |
| return mImpl; |
| } |
| |
| private ExternalInterfaceBinder createExternalInterface() { |
| return new IStartingWindowImpl(this); |
| } |
| |
| private void onInit() { |
| mShellTaskOrganizer.initStartingWindow(this); |
| mShellController.addExternalInterface(KEY_EXTRA_SHELL_STARTING_WINDOW, |
| this::createExternalInterface, this); |
| } |
| |
| @Override |
| public Context getContext() { |
| return mContext; |
| } |
| |
| @Override |
| public ShellExecutor getRemoteCallExecutor() { |
| return mSplashScreenExecutor; |
| } |
| |
| /* |
| * Registers the starting window listener. |
| * |
| * @param listener The callback when need a starting window. |
| */ |
| @VisibleForTesting |
| void setStartingWindowListener(TriConsumer<Integer, Integer, Integer> listener) { |
| mTaskLaunchingCallback = listener; |
| } |
| |
| @VisibleForTesting |
| boolean hasStartingWindowListener() { |
| return mTaskLaunchingCallback != null; |
| } |
| |
| /** |
| * Called when a task need a starting window. |
| */ |
| public void addStartingWindow(StartingWindowInfo windowInfo) { |
| mSplashScreenExecutor.execute(() -> { |
| Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "addStartingWindow"); |
| |
| final int suggestionType = mStartingWindowTypeAlgorithm.getSuggestedWindowType( |
| windowInfo); |
| final RunningTaskInfo runningTaskInfo = windowInfo.taskInfo; |
| if (suggestionType == STARTING_WINDOW_TYPE_WINDOWLESS) { |
| mStartingSurfaceDrawer.addWindowlessStartingSurface(windowInfo); |
| } else if (isSplashScreenType(suggestionType)) { |
| mStartingSurfaceDrawer.addSplashScreenStartingWindow(windowInfo, suggestionType); |
| } else if (suggestionType == STARTING_WINDOW_TYPE_SNAPSHOT) { |
| final TaskSnapshot snapshot = windowInfo.taskSnapshot; |
| mStartingSurfaceDrawer.makeTaskSnapshotWindow(windowInfo, snapshot); |
| } |
| if (suggestionType != STARTING_WINDOW_TYPE_NONE |
| && suggestionType != STARTING_WINDOW_TYPE_WINDOWLESS) { |
| int taskId = runningTaskInfo.taskId; |
| int color = mStartingSurfaceDrawer |
| .getStartingWindowBackgroundColorForTask(taskId); |
| if (color != Color.TRANSPARENT) { |
| synchronized (mTaskBackgroundColors) { |
| mTaskBackgroundColors.append(taskId, color); |
| } |
| } |
| if (mTaskLaunchingCallback != null && isSplashScreenType(suggestionType)) { |
| mTaskLaunchingCallback.accept(taskId, suggestionType, color); |
| } |
| } |
| |
| Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); |
| }); |
| } |
| |
| private static boolean isSplashScreenType(@StartingWindowType int suggestionType) { |
| return suggestionType == STARTING_WINDOW_TYPE_SPLASH_SCREEN |
| || suggestionType == STARTING_WINDOW_TYPE_SOLID_COLOR_SPLASH_SCREEN |
| || suggestionType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN; |
| } |
| |
| public void copySplashScreenView(int taskId) { |
| mSplashScreenExecutor.execute(() -> { |
| mStartingSurfaceDrawer.copySplashScreenView(taskId); |
| }); |
| } |
| |
| /** |
| * @see StartingSurfaceDrawer#onAppSplashScreenViewRemoved(int) |
| */ |
| public void onAppSplashScreenViewRemoved(int taskId) { |
| mSplashScreenExecutor.execute( |
| () -> mStartingSurfaceDrawer.onAppSplashScreenViewRemoved(taskId)); |
| } |
| |
| /** |
| * Called when the IME has drawn on the organized task. |
| */ |
| public void onImeDrawnOnTask(int taskId) { |
| mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.onImeDrawnOnTask(taskId)); |
| } |
| |
| /** |
| * Called when the content of a task is ready to show, starting window can be removed. |
| */ |
| public void removeStartingWindow(StartingWindowRemovalInfo removalInfo) { |
| mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.removeStartingWindow( |
| removalInfo)); |
| if (!removalInfo.windowlessSurface) { |
| mSplashScreenExecutor.executeDelayed(() -> { |
| synchronized (mTaskBackgroundColors) { |
| mTaskBackgroundColors.delete(removalInfo.taskId); |
| } |
| }, TASK_BG_COLOR_RETAIN_TIME_MS); |
| } |
| } |
| |
| /** |
| * Clear all starting window immediately, called this method when releasing the task organizer. |
| */ |
| public void clearAllWindows() { |
| mSplashScreenExecutor.execute(() -> { |
| mStartingSurfaceDrawer.clearAllWindows(); |
| synchronized (mTaskBackgroundColors) { |
| mTaskBackgroundColors.clear(); |
| } |
| }); |
| } |
| |
| /** |
| * The interface for calls from outside the Shell, within the host process. |
| */ |
| private class StartingSurfaceImpl implements StartingSurface { |
| @Override |
| public int getBackgroundColor(TaskInfo taskInfo) { |
| synchronized (mTaskBackgroundColors) { |
| final int index = mTaskBackgroundColors.indexOfKey(taskInfo.taskId); |
| if (index >= 0) { |
| return mTaskBackgroundColors.valueAt(index); |
| } |
| } |
| final int color = mStartingSurfaceDrawer.estimateTaskBackgroundColor(taskInfo); |
| return color != Color.TRANSPARENT |
| ? color : SplashscreenContentDrawer.getSystemBGColor(); |
| } |
| |
| @Override |
| public void setSysuiProxy(SysuiProxy proxy) { |
| mSplashScreenExecutor.execute(() -> mStartingSurfaceDrawer.setSysuiProxy(proxy)); |
| } |
| } |
| |
| /** |
| * The interface for calls from outside the host process. |
| */ |
| @BinderThread |
| private static class IStartingWindowImpl extends IStartingWindow.Stub |
| implements ExternalInterfaceBinder { |
| private StartingWindowController mController; |
| private SingleInstanceRemoteListener<StartingWindowController, |
| IStartingWindowListener> mListener; |
| private final TriConsumer<Integer, Integer, Integer> mStartingWindowListener = |
| (taskId, supportedType, startingWindowBackgroundColor) -> { |
| mListener.call(l -> l.onTaskLaunching(taskId, supportedType, |
| startingWindowBackgroundColor)); |
| }; |
| |
| public IStartingWindowImpl(StartingWindowController controller) { |
| mController = controller; |
| mListener = new SingleInstanceRemoteListener<>(controller, |
| c -> c.setStartingWindowListener(mStartingWindowListener), |
| c -> c.setStartingWindowListener(null)); |
| } |
| |
| /** |
| * Invalidates this instance, preventing future calls from updating the controller. |
| */ |
| @Override |
| public void invalidate() { |
| mController = null; |
| // Unregister the listener to ensure any registered binder death recipients are unlinked |
| mListener.unregister(); |
| } |
| |
| @Override |
| public void setStartingWindowListener(IStartingWindowListener listener) { |
| executeRemoteCallWithTaskPermission(mController, "setStartingWindowListener", |
| (controller) -> { |
| if (listener != null) { |
| mListener.register(listener); |
| } else { |
| mListener.unregister(); |
| } |
| }); |
| } |
| } |
| } |