1 /*
2  * Copyright (C) 2017 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 com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_POSITIONING;
20 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
21 
22 import static java.util.concurrent.CompletableFuture.completedFuture;
23 
24 import android.annotation.Nullable;
25 import android.graphics.Point;
26 import android.graphics.Rect;
27 import android.util.Slog;
28 import android.view.Display;
29 import android.view.IWindow;
30 import android.view.InputWindowHandle;
31 import android.view.SurfaceControl;
32 
33 import java.util.concurrent.CompletableFuture;
34 
35 /**
36  * Controller for task positioning by drag.
37  */
38 class TaskPositioningController {
39     private final WindowManagerService mService;
40     private SurfaceControl mInputSurface;
41     private DisplayContent mPositioningDisplay;
42 
43     private @Nullable TaskPositioner mTaskPositioner;
44 
45     private final Rect mTmpClipRect = new Rect();
46 
isPositioningLocked()47     boolean isPositioningLocked() {
48         return mTaskPositioner != null;
49     }
50 
51     final SurfaceControl.Transaction mTransaction;
52 
getDragWindowHandleLocked()53     InputWindowHandle getDragWindowHandleLocked() {
54         return mTaskPositioner != null ? mTaskPositioner.mDragWindowHandle : null;
55     }
56 
TaskPositioningController(WindowManagerService service)57     TaskPositioningController(WindowManagerService service) {
58         mService = service;
59         mTransaction = service.mTransactionFactory.get();
60     }
61 
hideInputSurface(int displayId)62     void hideInputSurface(int displayId) {
63         if (mPositioningDisplay != null && mPositioningDisplay.getDisplayId() == displayId
64                 && mInputSurface != null) {
65             mTransaction.hide(mInputSurface).apply();
66         }
67     }
68 
69     /**
70      * @return a future that completes after window info is sent.
71      */
showInputSurface(int displayId)72     CompletableFuture<Void> showInputSurface(int displayId) {
73         if (mPositioningDisplay == null || mPositioningDisplay.getDisplayId() != displayId) {
74             return completedFuture(null);
75         }
76         final DisplayContent dc = mService.mRoot.getDisplayContent(displayId);
77         if (mInputSurface == null) {
78             mInputSurface = mService.makeSurfaceBuilder(dc.getSession())
79                     .setContainerLayer()
80                     .setName("Drag and Drop Input Consumer")
81                     .setCallsite("TaskPositioningController.showInputSurface")
82                     .setParent(dc.getOverlayLayer())
83                     .build();
84         }
85 
86         final InputWindowHandle h = getDragWindowHandleLocked();
87         if (h == null) {
88             Slog.w(TAG_WM, "Drag is in progress but there is no "
89                     + "drag window handle.");
90             return completedFuture(null);
91         }
92 
93         final Display display = dc.getDisplay();
94         final Point p = new Point();
95         display.getRealSize(p);
96         mTmpClipRect.set(0, 0, p.x, p.y);
97 
98         CompletableFuture<Void> result = new CompletableFuture<>();
99         mTransaction.show(mInputSurface)
100                 .setInputWindowInfo(mInputSurface, h)
101                 .setLayer(mInputSurface, Integer.MAX_VALUE)
102                 .setPosition(mInputSurface, 0, 0)
103                 .setCrop(mInputSurface, mTmpClipRect)
104                 .addWindowInfosReportedListener(() -> result.complete(null))
105                 .apply();
106         return result;
107     }
108 
startMovingTask(IWindow window, float startX, float startY)109     boolean startMovingTask(IWindow window, float startX, float startY) {
110         WindowState win = null;
111         CompletableFuture<Boolean> startPositioningLockedFuture;
112         synchronized (mService.mGlobalLock) {
113             win = mService.windowForClientLocked(null, window, false);
114             startPositioningLockedFuture =
115                 startPositioningLocked(
116                     win, false /*resize*/, false /*preserveOrientation*/, startX, startY);
117         }
118 
119         try {
120             if (!startPositioningLockedFuture.get()) {
121                 return false;
122             }
123         } catch (Exception exception) {
124             Slog.e(TAG_WM, "Exception thrown while waiting for startPositionLocked future",
125                     exception);
126             return false;
127         }
128 
129         synchronized (mService.mGlobalLock) {
130             mService.mAtmService.setFocusedTask(win.getTask().mTaskId);
131         }
132         return true;
133     }
134 
handleTapOutsideTask(DisplayContent displayContent, int x, int y)135     void handleTapOutsideTask(DisplayContent displayContent, int x, int y) {
136         mService.mH.post(() -> {
137             Task task;
138             CompletableFuture<Boolean> startPositioningLockedFuture;
139             synchronized (mService.mGlobalLock) {
140                 task = displayContent.findTaskForResizePoint(x, y);
141                 if (task == null || !task.isResizeable()) {
142                     // The task is not resizable, so don't do anything when the user drags the
143                     // the resize handles.
144                     return;
145                 }
146                 startPositioningLockedFuture =
147                     startPositioningLocked(task.getTopVisibleAppMainWindow(), true /*resize*/,
148                             task.preserveOrientationOnResize(), x, y);
149             }
150 
151             try {
152                 if (!startPositioningLockedFuture.get()) {
153                     return;
154                 }
155             } catch (Exception exception) {
156                 Slog.e(TAG_WM, "Exception thrown while waiting for startPositionLocked future",
157                         exception);
158                 return;
159             }
160 
161             synchronized (mService.mGlobalLock) {
162                 mService.mAtmService.setFocusedTask(task.mTaskId);
163             }
164         });
165     }
166 
startPositioningLocked(WindowState win, boolean resize, boolean preserveOrientation, float startX, float startY)167     private CompletableFuture<Boolean> startPositioningLocked(WindowState win, boolean resize,
168             boolean preserveOrientation, float startX, float startY) {
169         if (DEBUG_TASK_POSITIONING)
170             Slog.d(TAG_WM, "startPositioningLocked: "
171                     + "win=" + win + ", resize=" + resize + ", preserveOrientation="
172                     + preserveOrientation + ", {" + startX + ", " + startY + "}");
173 
174         if (win == null || win.mActivityRecord == null) {
175             Slog.w(TAG_WM, "startPositioningLocked: Bad window " + win);
176             return completedFuture(false);
177         }
178         if (win.mInputChannel == null) {
179             Slog.wtf(TAG_WM, "startPositioningLocked: " + win + " has no input channel, "
180                     + " probably being removed");
181             return completedFuture(false);
182         }
183 
184         final DisplayContent displayContent = win.getDisplayContent();
185         if (displayContent == null) {
186             Slog.w(TAG_WM, "startPositioningLocked: Invalid display content " + win);
187             return completedFuture(false);
188         }
189         mPositioningDisplay = displayContent;
190 
191         mTaskPositioner = TaskPositioner.create(mService);
192         return mTaskPositioner.register(displayContent, win).thenApply(unused -> {
193             // The global lock is held by the callers of startPositioningLocked but released before
194             // the async results are waited on. We must acquire the lock in this callback to ensure
195             // thread safety.
196             synchronized (mService.mGlobalLock) {
197                 // We need to grab the touch focus so that the touch events during the
198                 // resizing/scrolling are not sent to the app. 'win' is the main window
199                 // of the app, it may not have focus since there might be other windows
200                 // on top (eg. a dialog window).
201                 WindowState transferFocusFromWin = win;
202                 if (displayContent.mCurrentFocus != null && displayContent.mCurrentFocus != win
203                         && displayContent.mCurrentFocus.mActivityRecord == win.mActivityRecord) {
204                     transferFocusFromWin = displayContent.mCurrentFocus;
205                 }
206                 if (!mService.mInputManager.transferTouchFocus(
207                         transferFocusFromWin.mInputChannel, mTaskPositioner.mClientChannel,
208                         false /* isDragDrop */)) {
209                     Slog.e(TAG_WM, "startPositioningLocked: Unable to transfer touch focus");
210                     cleanUpTaskPositioner();
211                     return false;
212                 }
213 
214                 mTaskPositioner.startDrag(resize, preserveOrientation, startX, startY);
215                 return true;
216             }
217         });
218     }
219 
220     public void finishTaskPositioning(IWindow window) {
221         if (mTaskPositioner != null && mTaskPositioner.mClientCallback == window.asBinder()) {
222             finishTaskPositioning();
223         }
224     }
225 
226     void finishTaskPositioning() {
227         // TaskPositioner attaches the InputEventReceiver to the animation thread. We need to
228         // dispose the receiver on the same thread to avoid race conditions.
229         mService.mAnimationHandler.post(() -> {
230             if (DEBUG_TASK_POSITIONING) Slog.d(TAG_WM, "finishPositioning");
231 
232             synchronized (mService.mGlobalLock) {
233                 cleanUpTaskPositioner();
234                 mPositioningDisplay = null;
235             }
236         });
237     }
238 
239     private void cleanUpTaskPositioner() {
240         final TaskPositioner positioner = mTaskPositioner;
241         if (positioner == null) {
242             return;
243         }
244 
245         // We need to assign task positioner to null first to indicate that we're finishing task
246         // positioning.
247         mTaskPositioner = null;
248         positioner.unregister();
249     }
250 }
251