DragState.java revision 2d5618c22101cfc4d6478cfe1d846798389540c1
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 17package com.android.server.wm; 18 19import com.android.server.input.InputApplicationHandle; 20import com.android.server.input.InputWindowHandle; 21import com.android.server.wm.WindowManagerService.DragInputEventReceiver; 22import com.android.server.wm.WindowManagerService.H; 23 24import android.content.ClipData; 25import android.content.ClipDescription; 26import android.graphics.Point; 27import android.graphics.Region; 28import android.os.IBinder; 29import android.os.Message; 30import android.os.Process; 31import android.os.RemoteException; 32import android.util.Slog; 33import android.view.Display; 34import android.view.DragEvent; 35import android.view.InputChannel; 36import android.view.Surface; 37import android.view.View; 38import android.view.WindowManager; 39 40import java.util.ArrayList; 41 42/** 43 * Drag/drop state 44 */ 45class DragState { 46 final WindowManagerService mService; 47 IBinder mToken; 48 Surface mSurface; 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 67 DragState(WindowManagerService service, IBinder token, Surface surface, 68 int flags, IBinder localWin) { 69 mService = service; 70 mToken = token; 71 mSurface = surface; 72 mFlags = flags; 73 mLocalWin = localWin; 74 mNotifiedWindows = new ArrayList<WindowState>(); 75 } 76 77 void reset() { 78 if (mSurface != null) { 79 mSurface.destroy(); 80 } 81 mSurface = 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 */ 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 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 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 */ 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 */ 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 */ 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 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 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 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 Surface.openTransaction(); 297 try { 298 mSurface.setPosition(x - mThumbOffsetX, y - mThumbOffsetY); 299 if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DRAG " 300 + mSurface + ": pos=(" + 301 (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")"); 302 } finally { 303 Surface.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. 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 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 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}