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