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