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