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