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