1 /*
2  * Copyright (C) 2020 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 
17 package com.android.server.wm;
18 
19 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_CREATED;
20 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ACTIVITY_DRAWN;
21 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN;
22 import static android.window.StartingWindowInfo.TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
23 import static android.window.StartingWindowInfo.TYPE_PARAMETER_APP_PREFERS_ICON;
24 import static android.window.StartingWindowInfo.TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
25 import static android.window.StartingWindowInfo.TYPE_PARAMETER_NEW_TASK;
26 import static android.window.StartingWindowInfo.TYPE_PARAMETER_PROCESS_RUNNING;
27 import static android.window.StartingWindowInfo.TYPE_PARAMETER_TASK_SWITCH;
28 import static android.window.StartingWindowInfo.TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
29 
30 import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_TYPE_SNAPSHOT;
31 import static com.android.server.wm.ActivityRecord.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
32 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
33 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
34 
35 import android.annotation.NonNull;
36 import android.annotation.Nullable;
37 import android.app.ActivityOptions;
38 import android.app.compat.CompatChanges;
39 import android.compat.annotation.ChangeId;
40 import android.compat.annotation.EnabledSince;
41 import android.content.pm.ApplicationInfo;
42 import android.os.UserHandle;
43 import android.util.Slog;
44 import android.window.ITaskOrganizer;
45 import android.window.SplashScreenView;
46 import android.window.TaskSnapshot;
47 
48 import java.util.ArrayList;
49 import java.util.function.Supplier;
50 
51 /**
52  * Managing to create and release a starting window surface.
53  */
54 public class StartingSurfaceController {
55     private static final String TAG = TAG_WITH_CLASS_NAME
56             ? StartingSurfaceController.class.getSimpleName() : TAG_WM;
57     /**
58      * Application is allowed to receive the
59      * {@link
60      * android.window.SplashScreen.OnExitAnimationListener#onSplashScreenExit(SplashScreenView)}
61      * callback, even when the splash screen only shows a solid color.
62      */
63     @ChangeId
64     @EnabledSince(targetSdkVersion = android.os.Build.VERSION_CODES.TIRAMISU)
65     private static final long ALLOW_COPY_SOLID_COLOR_VIEW = 205907456L;
66 
67     private final WindowManagerService mService;
68     private final SplashScreenExceptionList mSplashScreenExceptionsList;
69 
70     // Cache status while deferring add starting window
71     boolean mInitProcessRunning;
72     boolean mInitNewTask;
73     boolean mInitTaskSwitch;
74     private final ArrayList<DeferringStartingWindowRecord> mDeferringAddStartActivities =
75             new ArrayList<>();
76     private boolean mDeferringAddStartingWindow;
77 
StartingSurfaceController(WindowManagerService wm)78     public StartingSurfaceController(WindowManagerService wm) {
79         mService = wm;
80         mSplashScreenExceptionsList = new SplashScreenExceptionList(wm.mContext.getMainExecutor());
81     }
82 
createSplashScreenStartingSurface(ActivityRecord activity, int theme)83     StartingSurface createSplashScreenStartingSurface(ActivityRecord activity, int theme) {
84         final Task task = activity.getTask();
85         final TaskOrganizerController controller =
86                 mService.mAtmService.mTaskOrganizerController;
87         if (task != null && controller.addStartingWindow(task, activity, theme,
88                 null /* taskSnapshot */)) {
89             return new StartingSurface(task, controller.getTaskOrganizer());
90         }
91         return null;
92     }
93 
94     /**
95      * @see SplashScreenExceptionList#isException(String, int, Supplier)
96      */
isExceptionApp(@onNull String packageName, int targetSdk, @Nullable Supplier<ApplicationInfo> infoProvider)97     boolean isExceptionApp(@NonNull String packageName, int targetSdk,
98             @Nullable Supplier<ApplicationInfo> infoProvider) {
99         return mSplashScreenExceptionsList.isException(packageName, targetSdk, infoProvider);
100     }
101 
makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch, boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated, boolean isSolidColor, boolean useLegacy, boolean activityDrawn, int startingWindowType, boolean appPrefersIcon, String packageName, int userId)102     static int makeStartingWindowTypeParameter(boolean newTask, boolean taskSwitch,
103             boolean processRunning, boolean allowTaskSnapshot, boolean activityCreated,
104             boolean isSolidColor, boolean useLegacy, boolean activityDrawn, int startingWindowType,
105             boolean appPrefersIcon, String packageName, int userId) {
106         int parameter = 0;
107         if (newTask) {
108             parameter |= TYPE_PARAMETER_NEW_TASK;
109         }
110         if (taskSwitch) {
111             parameter |= TYPE_PARAMETER_TASK_SWITCH;
112         }
113         if (processRunning) {
114             parameter |= TYPE_PARAMETER_PROCESS_RUNNING;
115         }
116         if (allowTaskSnapshot) {
117             parameter |= TYPE_PARAMETER_ALLOW_TASK_SNAPSHOT;
118         }
119         if (activityCreated || startingWindowType == STARTING_WINDOW_TYPE_SNAPSHOT) {
120             parameter |= TYPE_PARAMETER_ACTIVITY_CREATED;
121         }
122         if (isSolidColor) {
123             parameter |= TYPE_PARAMETER_USE_SOLID_COLOR_SPLASH_SCREEN;
124         }
125         if (useLegacy) {
126             parameter |= TYPE_PARAMETER_LEGACY_SPLASH_SCREEN;
127         }
128         if (activityDrawn) {
129             parameter |= TYPE_PARAMETER_ACTIVITY_DRAWN;
130         }
131         if (startingWindowType == STARTING_WINDOW_TYPE_SPLASH_SCREEN
132                 && CompatChanges.isChangeEnabled(ALLOW_COPY_SOLID_COLOR_VIEW, packageName,
133                 UserHandle.of(userId))) {
134             parameter |= TYPE_PARAMETER_ALLOW_HANDLE_SOLID_COLOR_SCREEN;
135         }
136         if (appPrefersIcon) {
137             parameter |= TYPE_PARAMETER_APP_PREFERS_ICON;
138         }
139         return parameter;
140     }
141 
createTaskSnapshotSurface(ActivityRecord activity, TaskSnapshot taskSnapshot)142     StartingSurface createTaskSnapshotSurface(ActivityRecord activity, TaskSnapshot taskSnapshot) {
143         final Task task = activity.getTask();
144         if (task == null) {
145             Slog.w(TAG, "TaskSnapshotSurface.create: Failed to find task for activity="
146                     + activity);
147             return null;
148         }
149         final WindowState mainWindow = activity.findMainWindow(false);
150         if (mainWindow == null) {
151             Slog.w(TAG, "TaskSnapshotSurface.create: no main window in " + activity);
152             return null;
153         }
154         if (activity.mDisplayContent.getRotation() != taskSnapshot.getRotation()) {
155             // The snapshot should have been checked by ActivityRecord#isSnapshotCompatible
156             // that the activity will be updated to the same rotation as the snapshot. Since
157             // the transition is not started yet, fixed rotation transform needs to be applied
158             // earlier to make the snapshot show in a rotated container.
159             activity.mDisplayContent.handleTopActivityLaunchingInDifferentOrientation(
160                     activity, false /* checkOpening */);
161         }
162         final TaskOrganizerController controller =
163                 mService.mAtmService.mTaskOrganizerController;
164         if (controller.addStartingWindow(task, activity, 0 /* launchTheme */, taskSnapshot)) {
165             return new StartingSurface(task, controller.getTaskOrganizer());
166         }
167         return null;
168     }
169 
170     private static final class DeferringStartingWindowRecord {
171         final ActivityRecord mDeferring;
172         final ActivityRecord mPrev;
173         final ActivityRecord mSource;
174 
DeferringStartingWindowRecord(ActivityRecord deferring, ActivityRecord prev, ActivityRecord source)175         DeferringStartingWindowRecord(ActivityRecord deferring, ActivityRecord prev,
176                 ActivityRecord source) {
177             mDeferring = deferring;
178             mPrev = prev;
179             mSource = source;
180         }
181     }
182 
183     /**
184      * Shows a starting window while starting a new activity. Do not use this method to create a
185      * starting window for an existing activity.
186      */
showStartingWindow(ActivityRecord target, ActivityRecord prev, boolean newTask, boolean isTaskSwitch, ActivityRecord source)187     void showStartingWindow(ActivityRecord target, ActivityRecord prev,
188             boolean newTask, boolean isTaskSwitch, ActivityRecord source) {
189         if (mDeferringAddStartingWindow) {
190             addDeferringRecord(target, prev, newTask, isTaskSwitch, source);
191         } else {
192             target.showStartingWindow(prev, newTask, isTaskSwitch, true /* startActivity */,
193                     source);
194         }
195     }
196 
197     /**
198      * Queueing the starting activity status while deferring add starting window.
199      * @see Task#startActivityLocked
200      */
addDeferringRecord(ActivityRecord deferring, ActivityRecord prev, boolean newTask, boolean isTaskSwitch, ActivityRecord source)201     private void addDeferringRecord(ActivityRecord deferring, ActivityRecord prev,
202             boolean newTask, boolean isTaskSwitch, ActivityRecord source) {
203         // Set newTask, taskSwitch, processRunning form first activity because those can change
204         // after first activity started.
205         if (mDeferringAddStartActivities.isEmpty()) {
206             mInitProcessRunning = deferring.isProcessRunning();
207             mInitNewTask = newTask;
208             mInitTaskSwitch = isTaskSwitch;
209         }
210         mDeferringAddStartActivities.add(new DeferringStartingWindowRecord(
211                 deferring, prev, source));
212     }
213 
showStartingWindowFromDeferringActivities(ActivityOptions topOptions)214     private void showStartingWindowFromDeferringActivities(ActivityOptions topOptions) {
215         // Attempt to add starting window from the top-most activity.
216         for (int i = mDeferringAddStartActivities.size() - 1; i >= 0; --i) {
217             final DeferringStartingWindowRecord next = mDeferringAddStartActivities.get(i);
218             if (next.mDeferring.getTask() == null) {
219                 Slog.e(TAG, "No task exists: " + next.mDeferring.shortComponentName
220                         + " parent: " + next.mDeferring.getParent());
221                 continue;
222             }
223             next.mDeferring.showStartingWindow(next.mPrev, mInitNewTask, mInitTaskSwitch,
224                     mInitProcessRunning, true /* startActivity */, next.mSource, topOptions);
225             // If one succeeds, it is done.
226             if (next.mDeferring.mStartingData != null) {
227                 break;
228             }
229         }
230         mDeferringAddStartActivities.clear();
231     }
232 
233     /**
234      * Begin deferring add starting window in one pass.
235      * This is used to deferring add starting window while starting multiples activities because
236      * system only need to provide a starting window to the top-visible activity.
237      * Most call {@link #endDeferAddStartingWindow} when starting activities process finished.
238      * @see #endDeferAddStartingWindow()
239      */
beginDeferAddStartingWindow()240     void beginDeferAddStartingWindow() {
241         mDeferringAddStartingWindow = true;
242     }
243 
244     /**
245      * End deferring add starting window.
246      */
endDeferAddStartingWindow(ActivityOptions topOptions)247     void endDeferAddStartingWindow(ActivityOptions topOptions) {
248         mDeferringAddStartingWindow = false;
249         showStartingWindowFromDeferringActivities(topOptions);
250     }
251 
252     final class StartingSurface {
253         private final Task mTask;
254         // The task organizer which hold the client side reference of this surface.
255         final ITaskOrganizer mTaskOrganizer;
256 
StartingSurface(Task task, ITaskOrganizer taskOrganizer)257         StartingSurface(Task task, ITaskOrganizer taskOrganizer) {
258             mTask = task;
259             mTaskOrganizer = taskOrganizer;
260         }
261 
262         /**
263          * Removes the starting window surface. Do not hold the window manager lock when calling
264          * this method!
265          *
266          * @param animate Whether need to play the default exit animation for starting window.
267          * @param hasImeSurface Whether the starting window has IME surface.
268          */
remove(boolean animate, boolean hasImeSurface)269         public void remove(boolean animate, boolean hasImeSurface) {
270             synchronized (mService.mGlobalLock) {
271                 mService.mAtmService.mTaskOrganizerController.removeStartingWindow(mTask,
272                         mTaskOrganizer, animate, hasImeSurface);
273             }
274         }
275     }
276 }
277