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 DragState(WindowManagerService service, IBinder token, Surface surface, int flags, IBinder localWin)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 reset()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 register()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 unregister()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 getDragLayerLw()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 */ broadcastDragStartedLw(final float touchX, final float touchY)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 */ sendDragStartedLw(WindowState newWin, float touchX, float touchY, ClipDescription desc)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 */ sendDragStartedIfNeededLw(WindowState newWin)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 broadcastDragEndedLw()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 endDragLw()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 notifyMoveLw(float x, float y)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. notifyDropLw(float x, float y)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 getTouchedWinAtPointLw(float xf, float yf)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 obtainDragEvent(WindowState win, int action, float x, float y, Object localState, ClipDescription description, ClipData data, boolean result)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 }