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