1 /* 2 * Copyright (C) 2011 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 com.android.server.input.InputApplicationHandle; 20 import com.android.server.input.InputWindowHandle; 21 import com.android.server.wm.WindowManagerService.DragInputEventReceiver; 22 import com.android.server.wm.WindowManagerService.H; 23 24 import android.content.ClipData; 25 import android.content.ClipDescription; 26 import android.graphics.Point; 27 import android.graphics.Rect; 28 import android.graphics.Region; 29 import android.os.IBinder; 30 import android.os.Message; 31 import android.os.Process; 32 import android.os.RemoteException; 33 import android.util.Slog; 34 import android.view.Display; 35 import android.view.DragEvent; 36 import android.view.InputChannel; 37 import android.view.SurfaceControl; 38 import android.view.View; 39 import android.view.WindowManager; 40 41 import java.util.ArrayList; 42 43 /** 44 * Drag/drop state 45 */ 46 class DragState { 47 final WindowManagerService mService; 48 IBinder mToken; 49 SurfaceControl mSurfaceControl; 50 int mFlags; 51 IBinder mLocalWin; 52 ClipData mData; 53 ClipDescription mDataDescription; 54 boolean mDragResult; 55 float mCurrentX, mCurrentY; 56 float mThumbOffsetX, mThumbOffsetY; 57 InputChannel mServerChannel, mClientChannel; 58 DragInputEventReceiver mInputEventReceiver; 59 InputApplicationHandle mDragApplicationHandle; 60 InputWindowHandle mDragWindowHandle; 61 WindowState mTargetWindow; 62 ArrayList<WindowState> mNotifiedWindows; 63 boolean mDragInProgress; 64 Display mDisplay; 65 66 private final Region mTmpRegion = new Region(); 67 private final Rect mTmpRect = new Rect(); 68 DragState(WindowManagerService service, IBinder token, SurfaceControl surface, int flags, IBinder localWin)69 DragState(WindowManagerService service, IBinder token, SurfaceControl surface, 70 int flags, IBinder localWin) { 71 mService = service; 72 mToken = token; 73 mSurfaceControl = surface; 74 mFlags = flags; 75 mLocalWin = localWin; 76 mNotifiedWindows = new ArrayList<WindowState>(); 77 } 78 reset()79 void reset() { 80 if (mSurfaceControl != null) { 81 mSurfaceControl.destroy(); 82 } 83 mSurfaceControl = null; 84 mFlags = 0; 85 mLocalWin = null; 86 mToken = null; 87 mData = null; 88 mThumbOffsetX = mThumbOffsetY = 0; 89 mNotifiedWindows = null; 90 } 91 92 /** 93 * @param display The Display that the window being dragged is on. 94 */ register(Display display)95 void register(Display display) { 96 mDisplay = display; 97 if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "registering drag input channel"); 98 if (mClientChannel != null) { 99 Slog.e(WindowManagerService.TAG, "Duplicate register of drag input channel"); 100 } else { 101 InputChannel[] channels = InputChannel.openInputChannelPair("drag"); 102 mServerChannel = channels[0]; 103 mClientChannel = channels[1]; 104 mService.mInputManager.registerInputChannel(mServerChannel, null); 105 mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel, 106 mService.mH.getLooper()); 107 108 mDragApplicationHandle = new InputApplicationHandle(null); 109 mDragApplicationHandle.name = "drag"; 110 mDragApplicationHandle.dispatchingTimeoutNanos = 111 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; 112 113 mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, 114 mDisplay.getDisplayId()); 115 mDragWindowHandle.name = "drag"; 116 mDragWindowHandle.inputChannel = mServerChannel; 117 mDragWindowHandle.layer = getDragLayerLw(); 118 mDragWindowHandle.layoutParamsFlags = 0; 119 mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; 120 mDragWindowHandle.dispatchingTimeoutNanos = 121 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; 122 mDragWindowHandle.visible = true; 123 mDragWindowHandle.canReceiveKeys = false; 124 mDragWindowHandle.hasFocus = true; 125 mDragWindowHandle.hasWallpaper = false; 126 mDragWindowHandle.paused = false; 127 mDragWindowHandle.ownerPid = Process.myPid(); 128 mDragWindowHandle.ownerUid = Process.myUid(); 129 mDragWindowHandle.inputFeatures = 0; 130 mDragWindowHandle.scaleFactor = 1.0f; 131 132 // The drag window cannot receive new touches. 133 mDragWindowHandle.touchableRegion.setEmpty(); 134 135 // The drag window covers the entire display 136 mDragWindowHandle.frameLeft = 0; 137 mDragWindowHandle.frameTop = 0; 138 Point p = new Point(); 139 mDisplay.getRealSize(p); 140 mDragWindowHandle.frameRight = p.x; 141 mDragWindowHandle.frameBottom = p.y; 142 143 // Pause rotations before a drag. 144 if (WindowManagerService.DEBUG_ORIENTATION) { 145 Slog.d(WindowManagerService.TAG, "Pausing rotation during drag"); 146 } 147 mService.pauseRotationLocked(); 148 } 149 } 150 unregister()151 void unregister() { 152 if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "unregistering drag input channel"); 153 if (mClientChannel == null) { 154 Slog.e(WindowManagerService.TAG, "Unregister of nonexistent drag input channel"); 155 } else { 156 mService.mInputManager.unregisterInputChannel(mServerChannel); 157 mInputEventReceiver.dispose(); 158 mInputEventReceiver = null; 159 mClientChannel.dispose(); 160 mServerChannel.dispose(); 161 mClientChannel = null; 162 mServerChannel = null; 163 164 mDragWindowHandle = null; 165 mDragApplicationHandle = null; 166 167 // Resume rotations after a drag. 168 if (WindowManagerService.DEBUG_ORIENTATION) { 169 Slog.d(WindowManagerService.TAG, "Resuming rotation after drag"); 170 } 171 mService.resumeRotationLocked(); 172 } 173 } 174 getDragLayerLw()175 int getDragLayerLw() { 176 return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG) 177 * WindowManagerService.TYPE_LAYER_MULTIPLIER 178 + WindowManagerService.TYPE_LAYER_OFFSET; 179 } 180 181 /* call out to each visible window/session informing it about the drag 182 */ broadcastDragStartedLw(final float touchX, final float touchY)183 void broadcastDragStartedLw(final float touchX, final float touchY) { 184 // Cache a base-class instance of the clip metadata so that parceling 185 // works correctly in calling out to the apps. 186 mDataDescription = (mData != null) ? mData.getDescription() : null; 187 mNotifiedWindows.clear(); 188 mDragInProgress = true; 189 190 if (WindowManagerService.DEBUG_DRAG) { 191 Slog.d(WindowManagerService.TAG, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); 192 } 193 194 final WindowList windows = mService.getWindowListLocked(mDisplay); 195 if (windows != null) { 196 final int N = windows.size(); 197 for (int i = 0; i < N; i++) { 198 sendDragStartedLw(windows.get(i), touchX, touchY, mDataDescription); 199 } 200 } 201 } 202 203 /* helper - send a caller-provided event, presumed to be DRAG_STARTED, if the 204 * designated window is potentially a drop recipient. There are race situations 205 * around DRAG_ENDED broadcast, so we make sure that once we've declared that 206 * the drag has ended, we never send out another DRAG_STARTED for this drag action. 207 * 208 * This method clones the 'event' parameter if it's being delivered to the same 209 * process, so it's safe for the caller to call recycle() on the event afterwards. 210 */ sendDragStartedLw(WindowState newWin, float touchX, float touchY, ClipDescription desc)211 private void sendDragStartedLw(WindowState newWin, float touchX, float touchY, 212 ClipDescription desc) { 213 // Don't actually send the event if the drag is supposed to be pinned 214 // to the originating window but 'newWin' is not that window. 215 if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) { 216 final IBinder winBinder = newWin.mClient.asBinder(); 217 if (winBinder != mLocalWin) { 218 if (WindowManagerService.DEBUG_DRAG) { 219 Slog.d(WindowManagerService.TAG, "Not dispatching local DRAG_STARTED to " + newWin); 220 } 221 return; 222 } 223 } 224 225 if (mDragInProgress && newWin.isPotentialDragTarget()) { 226 DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED, 227 touchX, touchY, null, desc, null, false); 228 try { 229 newWin.mClient.dispatchDragEvent(event); 230 // track each window that we've notified that the drag is starting 231 mNotifiedWindows.add(newWin); 232 } catch (RemoteException e) { 233 Slog.w(WindowManagerService.TAG, "Unable to drag-start window " + newWin); 234 } finally { 235 // if the callee was local, the dispatch has already recycled the event 236 if (Process.myPid() != newWin.mSession.mPid) { 237 event.recycle(); 238 } 239 } 240 } 241 } 242 243 /* helper - construct and send a DRAG_STARTED event only if the window has not 244 * previously been notified, i.e. it became visible after the drag operation 245 * was begun. This is a rare case. 246 */ sendDragStartedIfNeededLw(WindowState newWin)247 void sendDragStartedIfNeededLw(WindowState newWin) { 248 if (mDragInProgress) { 249 // If we have sent the drag-started, we needn't do so again 250 for (WindowState ws : mNotifiedWindows) { 251 if (ws == newWin) { 252 return; 253 } 254 } 255 if (WindowManagerService.DEBUG_DRAG) { 256 Slog.d(WindowManagerService.TAG, "need to send DRAG_STARTED to new window " + newWin); 257 } 258 sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription); 259 } 260 } 261 broadcastDragEndedLw()262 void broadcastDragEndedLw() { 263 if (WindowManagerService.DEBUG_DRAG) { 264 Slog.d(WindowManagerService.TAG, "broadcasting DRAG_ENDED"); 265 } 266 DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, 267 0, 0, null, null, null, mDragResult); 268 for (WindowState ws: mNotifiedWindows) { 269 try { 270 ws.mClient.dispatchDragEvent(evt); 271 } catch (RemoteException e) { 272 Slog.w(WindowManagerService.TAG, "Unable to drag-end window " + ws); 273 } 274 } 275 mNotifiedWindows.clear(); 276 mDragInProgress = false; 277 evt.recycle(); 278 } 279 endDragLw()280 void endDragLw() { 281 mService.mDragState.broadcastDragEndedLw(); 282 283 // stop intercepting input 284 mService.mDragState.unregister(); 285 mService.mInputMonitor.updateInputWindowsLw(true /*force*/); 286 287 // free our resources and drop all the object references 288 mService.mDragState.reset(); 289 mService.mDragState = null; 290 } 291 notifyMoveLw(float x, float y)292 void notifyMoveLw(float x, float y) { 293 final int myPid = Process.myPid(); 294 295 // Move the surface to the given touch 296 if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i( 297 WindowManagerService.TAG, ">>> OPEN TRANSACTION notifyMoveLw"); 298 SurfaceControl.openTransaction(); 299 try { 300 mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY); 301 if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DRAG " 302 + mSurfaceControl + ": pos=(" + 303 (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")"); 304 } finally { 305 SurfaceControl.closeTransaction(); 306 if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i( 307 WindowManagerService.TAG, "<<< CLOSE TRANSACTION notifyMoveLw"); 308 } 309 310 // Tell the affected window 311 WindowState touchedWin = getTouchedWinAtPointLw(x, y); 312 if (touchedWin == null) { 313 if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "No touched win at x=" + x + " y=" + y); 314 return; 315 } 316 if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) { 317 final IBinder touchedBinder = touchedWin.mClient.asBinder(); 318 if (touchedBinder != mLocalWin) { 319 // This drag is pinned only to the originating window, but the drag 320 // point is outside that window. Pretend it's over empty space. 321 touchedWin = null; 322 } 323 } 324 try { 325 // have we dragged over a new window? 326 if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) { 327 if (WindowManagerService.DEBUG_DRAG) { 328 Slog.d(WindowManagerService.TAG, "sending DRAG_EXITED to " + mTargetWindow); 329 } 330 // force DRAG_EXITED_EVENT if appropriate 331 DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED, 332 x, y, null, null, null, false); 333 mTargetWindow.mClient.dispatchDragEvent(evt); 334 if (myPid != mTargetWindow.mSession.mPid) { 335 evt.recycle(); 336 } 337 } 338 if (touchedWin != null) { 339 if (false && WindowManagerService.DEBUG_DRAG) { 340 Slog.d(WindowManagerService.TAG, "sending DRAG_LOCATION to " + touchedWin); 341 } 342 DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DRAG_LOCATION, 343 x, y, null, null, null, false); 344 touchedWin.mClient.dispatchDragEvent(evt); 345 if (myPid != touchedWin.mSession.mPid) { 346 evt.recycle(); 347 } 348 } 349 } catch (RemoteException e) { 350 Slog.w(WindowManagerService.TAG, "can't send drag notification to windows"); 351 } 352 mTargetWindow = touchedWin; 353 } 354 355 // Tell the drop target about the data. Returns 'true' if we can immediately 356 // dispatch the global drag-ended message, 'false' if we need to wait for a 357 // result from the recipient. notifyDropLw(float x, float y)358 boolean notifyDropLw(float x, float y) { 359 WindowState touchedWin = getTouchedWinAtPointLw(x, y); 360 if (touchedWin == null) { 361 // "drop" outside a valid window -- no recipient to apply a 362 // timeout to, and we can send the drag-ended message immediately. 363 mDragResult = false; 364 return true; 365 } 366 367 if (WindowManagerService.DEBUG_DRAG) { 368 Slog.d(WindowManagerService.TAG, "sending DROP to " + touchedWin); 369 } 370 final int myPid = Process.myPid(); 371 final IBinder token = touchedWin.mClient.asBinder(); 372 DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y, 373 null, null, mData, false); 374 try { 375 touchedWin.mClient.dispatchDragEvent(evt); 376 377 // 5 second timeout for this window to respond to the drop 378 mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token); 379 Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token); 380 mService.mH.sendMessageDelayed(msg, 5000); 381 } catch (RemoteException e) { 382 Slog.w(WindowManagerService.TAG, "can't send drop notification to win " + touchedWin); 383 return true; 384 } finally { 385 if (myPid != touchedWin.mSession.mPid) { 386 evt.recycle(); 387 } 388 } 389 mToken = token; 390 return false; 391 } 392 393 // Find the visible, touch-deliverable window under the given point getTouchedWinAtPointLw(float xf, float yf)394 private WindowState getTouchedWinAtPointLw(float xf, float yf) { 395 WindowState touchedWin = null; 396 final int x = (int) xf; 397 final int y = (int) yf; 398 399 final WindowList windows = mService.getWindowListLocked(mDisplay); 400 if (windows == null) { 401 return null; 402 } 403 final int N = windows.size(); 404 for (int i = N - 1; i >= 0; i--) { 405 WindowState child = windows.get(i); 406 final int flags = child.mAttrs.flags; 407 if (!child.isVisibleLw()) { 408 // not visible == don't tell about drags 409 continue; 410 } 411 if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { 412 // not touchable == don't tell about drags 413 continue; 414 } 415 416 child.getStackBounds(mTmpRect); 417 if (!mTmpRect.contains(x, y)) { 418 // outside of this window's activity stack == don't tell about drags 419 continue; 420 } 421 422 child.getTouchableRegion(mTmpRegion); 423 424 final int touchFlags = flags & 425 (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 426 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); 427 if (mTmpRegion.contains(x, y) || touchFlags == 0) { 428 // Found it 429 touchedWin = child; 430 break; 431 } 432 } 433 434 return touchedWin; 435 } 436 obtainDragEvent(WindowState win, int action, float x, float y, Object localState, ClipDescription description, ClipData data, boolean result)437 private static DragEvent obtainDragEvent(WindowState win, int action, 438 float x, float y, Object localState, 439 ClipDescription description, ClipData data, boolean result) { 440 float winX = x - win.mFrame.left; 441 float winY = y - win.mFrame.top; 442 if (win.mEnforceSizeCompat) { 443 winX *= win.mGlobalScale; 444 winY *= win.mGlobalScale; 445 } 446 return DragEvent.obtain(action, winX, winY, localState, description, data, result); 447 } 448 }