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.wm.shell.startingsurface;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
20 import static android.graphics.Color.WHITE;
21 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
22 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
23 
24 import android.annotation.BinderThread;
25 import android.annotation.NonNull;
26 import android.app.ActivityManager;
27 import android.app.ActivityManager.TaskDescription;
28 import android.graphics.Paint;
29 import android.graphics.Point;
30 import android.graphics.Rect;
31 import android.os.Bundle;
32 import android.os.IBinder;
33 import android.os.RemoteException;
34 import android.os.Trace;
35 import android.util.MergedConfiguration;
36 import android.util.Slog;
37 import android.view.IWindowSession;
38 import android.view.InputChannel;
39 import android.view.InsetsSourceControl;
40 import android.view.InsetsState;
41 import android.view.SurfaceControl;
42 import android.view.View;
43 import android.view.WindowManager;
44 import android.view.WindowManagerGlobal;
45 import android.window.ClientWindowFrames;
46 import android.window.SnapshotDrawerUtils;
47 import android.window.StartingWindowInfo;
48 import android.window.TaskSnapshot;
49 
50 import com.android.internal.protolog.common.ProtoLog;
51 import com.android.internal.view.BaseIWindow;
52 import com.android.wm.shell.common.ShellExecutor;
53 import com.android.wm.shell.protolog.ShellProtoLogGroup;
54 
55 import java.lang.ref.WeakReference;
56 
57 /**
58  * This class represents a starting window that shows a snapshot.
59  *
60  * @hide
61  */
62 public class TaskSnapshotWindow {
63     private static final String TAG = StartingWindowController.TAG;
64     private static final String TITLE_FORMAT = "SnapshotStartingWindow for taskId=";
65 
66     private final Window mWindow;
67     private final Runnable mClearWindowHandler;
68     private final ShellExecutor mSplashScreenExecutor;
69     private final IWindowSession mSession;
70     private boolean mHasDrawn;
71     private final Paint mBackgroundPaint = new Paint();
72     private final int mOrientationOnCreation;
73 
74     private final boolean mHasImeSurface;
75 
create(StartingWindowInfo info, IBinder appToken, TaskSnapshot snapshot, ShellExecutor splashScreenExecutor, @NonNull Runnable clearWindowHandler)76     static TaskSnapshotWindow create(StartingWindowInfo info, IBinder appToken,
77             TaskSnapshot snapshot, ShellExecutor splashScreenExecutor,
78             @NonNull Runnable clearWindowHandler) {
79         final ActivityManager.RunningTaskInfo runningTaskInfo = info.taskInfo;
80         final int taskId = runningTaskInfo.taskId;
81 
82         // if we're in PIP we don't want to create the snapshot
83         if (runningTaskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
84             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
85                     "did not create taskSnapshot due to being in PIP");
86             return null;
87         }
88         ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
89                 "create taskSnapshot surface for task: %d", taskId);
90 
91         final InsetsState topWindowInsetsState = info.topOpaqueWindowInsetsState;
92 
93         final WindowManager.LayoutParams layoutParams = SnapshotDrawerUtils.createLayoutParameters(
94                 info, TITLE_FORMAT + taskId, TYPE_APPLICATION_STARTING,
95                 snapshot.getHardwareBuffer().getFormat(), appToken);
96         if (layoutParams == null) {
97             Slog.e(TAG, "TaskSnapshotWindow no layoutParams");
98             return null;
99         }
100 
101         final Point taskSize = snapshot.getTaskSize();
102         final Rect taskBounds = new Rect(0, 0, taskSize.x, taskSize.y);
103         final int orientation = snapshot.getOrientation();
104         final int displayId = runningTaskInfo.displayId;
105 
106         final IWindowSession session = WindowManagerGlobal.getWindowSession();
107         final SurfaceControl surfaceControl = new SurfaceControl();
108         final ClientWindowFrames tmpFrames = new ClientWindowFrames();
109 
110         final InsetsSourceControl.Array tmpControls = new InsetsSourceControl.Array();
111         final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
112 
113         final TaskDescription taskDescription =
114                 SnapshotDrawerUtils.getOrCreateTaskDescription(runningTaskInfo);
115 
116         final TaskSnapshotWindow snapshotSurface = new TaskSnapshotWindow(
117                 snapshot, taskDescription, orientation,
118                 clearWindowHandler, splashScreenExecutor);
119         final Window window = snapshotSurface.mWindow;
120 
121         final InsetsState tmpInsetsState = new InsetsState();
122         final InputChannel tmpInputChannel = new InputChannel();
123         final float[] sizeCompatScale = { 1f };
124 
125         try {
126             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay");
127             final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
128                     info.requestedVisibleTypes, tmpInputChannel, tmpInsetsState, tmpControls,
129                     new Rect(), sizeCompatScale);
130             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
131             if (res < 0) {
132                 Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
133                 return null;
134             }
135         } catch (RemoteException e) {
136             snapshotSurface.clearWindowSynced();
137         }
138         window.setOuter(snapshotSurface);
139         try {
140             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
141             session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0, 0, 0,
142                     tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
143                     tmpControls, new Bundle());
144             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
145         } catch (RemoteException e) {
146             snapshotSurface.clearWindowSynced();
147             Slog.w(TAG, "Failed to relayout snapshot starting window");
148             return null;
149         }
150 
151         SnapshotDrawerUtils.drawSnapshotOnSurface(info, layoutParams, surfaceControl, snapshot,
152                 taskBounds, tmpFrames.frame, topWindowInsetsState, true /* releaseAfterDraw */);
153         snapshotSurface.mHasDrawn = true;
154         snapshotSurface.reportDrawn();
155 
156         return snapshotSurface;
157     }
158 
TaskSnapshotWindow(TaskSnapshot snapshot, TaskDescription taskDescription, int currentOrientation, Runnable clearWindowHandler, ShellExecutor splashScreenExecutor)159     public TaskSnapshotWindow(TaskSnapshot snapshot, TaskDescription taskDescription,
160             int currentOrientation, Runnable clearWindowHandler,
161             ShellExecutor splashScreenExecutor) {
162         mSplashScreenExecutor = splashScreenExecutor;
163         mSession = WindowManagerGlobal.getWindowSession();
164         mWindow = new Window();
165         mWindow.setSession(mSession);
166         int backgroundColor = taskDescription.getBackgroundColor();
167         mBackgroundPaint.setColor(backgroundColor != 0 ? backgroundColor : WHITE);
168         mOrientationOnCreation = currentOrientation;
169         mClearWindowHandler = clearWindowHandler;
170         mHasImeSurface = snapshot.hasImeSurface();
171     }
172 
getBackgroundColor()173     int getBackgroundColor() {
174         return mBackgroundPaint.getColor();
175     }
176 
hasImeSurface()177     boolean hasImeSurface() {
178 	return mHasImeSurface;
179     }
180 
removeImmediately()181     void removeImmediately() {
182         try {
183             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_STARTING_WINDOW,
184                     "Removing taskSnapshot surface, mHasDrawn=%b", mHasDrawn);
185             mSession.remove(mWindow);
186         } catch (RemoteException e) {
187             // nothing
188         }
189     }
190 
191     /**
192      * Clear window from drawer, must be post on main executor.
193      */
clearWindowSynced()194     private void clearWindowSynced() {
195         mSplashScreenExecutor.executeDelayed(mClearWindowHandler, 0);
196     }
197 
reportDrawn()198     private void reportDrawn() {
199         try {
200             mSession.finishDrawing(mWindow, null /* postDrawTransaction */, Integer.MAX_VALUE);
201         } catch (RemoteException e) {
202             clearWindowSynced();
203         }
204     }
205 
206     static class Window extends BaseIWindow {
207         private WeakReference<TaskSnapshotWindow> mOuter;
208 
setOuter(TaskSnapshotWindow outer)209         public void setOuter(TaskSnapshotWindow outer) {
210             mOuter = new WeakReference<>(outer);
211         }
212 
213         @BinderThread
214         @Override
resized(ClientWindowFrames frames, boolean reportDraw, MergedConfiguration mergedConfiguration, InsetsState insetsState, boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId, boolean dragResizing)215         public void resized(ClientWindowFrames frames, boolean reportDraw,
216                 MergedConfiguration mergedConfiguration, InsetsState insetsState,
217                 boolean forceLayout, boolean alwaysConsumeSystemBars, int displayId, int seqId,
218                 boolean dragResizing) {
219             final TaskSnapshotWindow snapshot = mOuter.get();
220             if (snapshot == null) {
221                 return;
222             }
223             snapshot.mSplashScreenExecutor.execute(() -> {
224                 if (mergedConfiguration != null
225                         && snapshot.mOrientationOnCreation
226                         != mergedConfiguration.getMergedConfiguration().orientation) {
227                     // The orientation of the screen is changing. We better remove the snapshot
228                     // ASAP as we are going to wait on the new window in any case to unfreeze
229                     // the screen, and the starting window is not needed anymore.
230                     snapshot.clearWindowSynced();
231                 } else if (reportDraw) {
232                     if (snapshot.mHasDrawn) {
233                         snapshot.reportDrawn();
234                     }
235                 }
236             });
237         }
238     }
239 }
240