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