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