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