DragState.java revision 598d40d93713470b7a4b02036cbea8ba9ee7ef90
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 static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG; 20import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; 21import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; 22import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; 23import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; 24 25import android.content.ClipData; 26import android.content.ClipDescription; 27import android.content.Context; 28import android.graphics.Matrix; 29import android.graphics.Point; 30import android.hardware.input.InputManager; 31import android.os.IBinder; 32import android.os.Message; 33import android.os.Process; 34import android.os.RemoteException; 35import android.os.ServiceManager; 36import android.os.UserHandle; 37import android.os.UserManager; 38import android.os.IUserManager; 39import android.util.Slog; 40import android.view.Display; 41import android.view.DragEvent; 42import android.view.InputChannel; 43import android.view.InputDevice; 44import android.view.PointerIcon; 45import android.view.SurfaceControl; 46import android.view.View; 47import android.view.WindowManager; 48import android.view.animation.AlphaAnimation; 49import android.view.animation.Animation; 50import android.view.animation.AnimationSet; 51import android.view.animation.DecelerateInterpolator; 52import android.view.animation.Interpolator; 53import android.view.animation.ScaleAnimation; 54import android.view.animation.Transformation; 55import android.view.animation.TranslateAnimation; 56 57import com.android.server.input.InputApplicationHandle; 58import com.android.server.input.InputWindowHandle; 59import com.android.server.wm.WindowManagerService.DragInputEventReceiver; 60import com.android.server.wm.WindowManagerService.H; 61 62import com.android.internal.view.IDropPermissions; 63 64import java.util.ArrayList; 65 66/** 67 * Drag/drop state 68 */ 69class DragState { 70 private static final long ANIMATION_DURATION_MS = 500; 71 72 private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ | 73 View.DRAG_FLAG_GLOBAL_URI_WRITE; 74 75 private static final int DRAG_FLAGS_URI_PERMISSIONS = DRAG_FLAGS_URI_ACCESS | 76 View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION | 77 View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION; 78 79 final WindowManagerService mService; 80 IBinder mToken; 81 SurfaceControl mSurfaceControl; 82 int mFlags; 83 IBinder mLocalWin; 84 int mPid; 85 int mUid; 86 int mSourceUserId; 87 boolean mCrossProfileCopyAllowed; 88 ClipData mData; 89 ClipDescription mDataDescription; 90 int mTouchSource; 91 boolean mDragResult; 92 float mOriginalAlpha; 93 float mOriginalX, mOriginalY; 94 float mCurrentX, mCurrentY; 95 float mThumbOffsetX, mThumbOffsetY; 96 InputChannel mServerChannel, mClientChannel; 97 DragInputEventReceiver mInputEventReceiver; 98 InputApplicationHandle mDragApplicationHandle; 99 InputWindowHandle mDragWindowHandle; 100 WindowState mTargetWindow; 101 ArrayList<WindowState> mNotifiedWindows; 102 boolean mDragInProgress; 103 DisplayContent mDisplayContent; 104 105 private Animation mAnimation; 106 final Transformation mTransformation = new Transformation(); 107 private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); 108 109 DragState(WindowManagerService service, IBinder token, SurfaceControl surface, 110 int flags, IBinder localWin) { 111 mService = service; 112 mToken = token; 113 mSurfaceControl = surface; 114 mFlags = flags; 115 mLocalWin = localWin; 116 mNotifiedWindows = new ArrayList<WindowState>(); 117 } 118 119 void reset() { 120 if (mSurfaceControl != null) { 121 mSurfaceControl.destroy(); 122 } 123 mSurfaceControl = null; 124 mFlags = 0; 125 mLocalWin = null; 126 mToken = null; 127 mData = null; 128 mThumbOffsetX = mThumbOffsetY = 0; 129 mNotifiedWindows = null; 130 } 131 132 /** 133 * @param display The Display that the window being dragged is on. 134 */ 135 void register(Display display) { 136 if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel"); 137 if (mClientChannel != null) { 138 Slog.e(TAG_WM, "Duplicate register of drag input channel"); 139 } else { 140 mDisplayContent = mService.getDisplayContentLocked(display.getDisplayId()); 141 142 InputChannel[] channels = InputChannel.openInputChannelPair("drag"); 143 mServerChannel = channels[0]; 144 mClientChannel = channels[1]; 145 mService.mInputManager.registerInputChannel(mServerChannel, null); 146 mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel, 147 mService.mH.getLooper()); 148 149 mDragApplicationHandle = new InputApplicationHandle(null); 150 mDragApplicationHandle.name = "drag"; 151 mDragApplicationHandle.dispatchingTimeoutNanos = 152 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; 153 154 mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, 155 display.getDisplayId()); 156 mDragWindowHandle.name = "drag"; 157 mDragWindowHandle.inputChannel = mServerChannel; 158 mDragWindowHandle.layer = getDragLayerLw(); 159 mDragWindowHandle.layoutParamsFlags = 0; 160 mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; 161 mDragWindowHandle.dispatchingTimeoutNanos = 162 WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; 163 mDragWindowHandle.visible = true; 164 mDragWindowHandle.canReceiveKeys = false; 165 mDragWindowHandle.hasFocus = true; 166 mDragWindowHandle.hasWallpaper = false; 167 mDragWindowHandle.paused = false; 168 mDragWindowHandle.ownerPid = Process.myPid(); 169 mDragWindowHandle.ownerUid = Process.myUid(); 170 mDragWindowHandle.inputFeatures = 0; 171 mDragWindowHandle.scaleFactor = 1.0f; 172 173 // The drag window cannot receive new touches. 174 mDragWindowHandle.touchableRegion.setEmpty(); 175 176 // The drag window covers the entire display 177 mDragWindowHandle.frameLeft = 0; 178 mDragWindowHandle.frameTop = 0; 179 Point p = new Point(); 180 display.getRealSize(p); 181 mDragWindowHandle.frameRight = p.x; 182 mDragWindowHandle.frameBottom = p.y; 183 184 // Pause rotations before a drag. 185 if (DEBUG_ORIENTATION) { 186 Slog.d(TAG_WM, "Pausing rotation during drag"); 187 } 188 mService.pauseRotationLocked(); 189 } 190 } 191 192 void unregister() { 193 if (DEBUG_DRAG) Slog.d(TAG_WM, "unregistering drag input channel"); 194 if (mClientChannel == null) { 195 Slog.e(TAG_WM, "Unregister of nonexistent drag input channel"); 196 } else { 197 mService.mInputManager.unregisterInputChannel(mServerChannel); 198 mInputEventReceiver.dispose(); 199 mInputEventReceiver = null; 200 mClientChannel.dispose(); 201 mServerChannel.dispose(); 202 mClientChannel = null; 203 mServerChannel = null; 204 205 mDragWindowHandle = null; 206 mDragApplicationHandle = null; 207 208 // Resume rotations after a drag. 209 if (DEBUG_ORIENTATION) { 210 Slog.d(TAG_WM, "Resuming rotation after drag"); 211 } 212 mService.resumeRotationLocked(); 213 } 214 } 215 216 int getDragLayerLw() { 217 return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG) 218 * WindowManagerService.TYPE_LAYER_MULTIPLIER 219 + WindowManagerService.TYPE_LAYER_OFFSET; 220 } 221 222 /* call out to each visible window/session informing it about the drag 223 */ 224 void broadcastDragStartedLw(final float touchX, final float touchY) { 225 mOriginalX = mCurrentX = touchX; 226 mOriginalY = mCurrentY = touchY; 227 228 // Cache a base-class instance of the clip metadata so that parceling 229 // works correctly in calling out to the apps. 230 mDataDescription = (mData != null) ? mData.getDescription() : null; 231 mNotifiedWindows.clear(); 232 mDragInProgress = true; 233 234 mSourceUserId = UserHandle.getUserId(mUid); 235 236 final IUserManager userManager = 237 (IUserManager) ServiceManager.getService(Context.USER_SERVICE); 238 try { 239 mCrossProfileCopyAllowed = !userManager.getUserRestrictions(mSourceUserId).getBoolean( 240 UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); 241 } catch (RemoteException e) { 242 Slog.e(TAG_WM, "Remote Exception calling UserManager: " + e); 243 mCrossProfileCopyAllowed = false; 244 } 245 246 if (DEBUG_DRAG) { 247 Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); 248 } 249 250 final WindowList windows = mDisplayContent.getWindowList(); 251 final int N = windows.size(); 252 for (int i = 0; i < N; i++) { 253 sendDragStartedLw(windows.get(i), touchX, touchY, mDataDescription); 254 } 255 } 256 257 /* helper - send a ACTION_DRAG_STARTED event, if the 258 * designated window is potentially a drop recipient. There are race situations 259 * around DRAG_ENDED broadcast, so we make sure that once we've declared that 260 * the drag has ended, we never send out another DRAG_STARTED for this drag action. 261 * 262 * This method clones the 'event' parameter if it's being delivered to the same 263 * process, so it's safe for the caller to call recycle() on the event afterwards. 264 */ 265 private void sendDragStartedLw(WindowState newWin, float touchX, float touchY, 266 ClipDescription desc) { 267 if (mDragInProgress && isValidDropTarget(newWin)) { 268 DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED, 269 touchX, touchY, null, desc, null, null, false); 270 try { 271 newWin.mClient.dispatchDragEvent(event); 272 // track each window that we've notified that the drag is starting 273 mNotifiedWindows.add(newWin); 274 } catch (RemoteException e) { 275 Slog.w(TAG_WM, "Unable to drag-start window " + newWin); 276 } finally { 277 // if the callee was local, the dispatch has already recycled the event 278 if (Process.myPid() != newWin.mSession.mPid) { 279 event.recycle(); 280 } 281 } 282 } 283 } 284 285 private boolean isValidDropTarget(WindowState targetWin) { 286 if (targetWin == null) { 287 return false; 288 } 289 if (!targetWin.isPotentialDragTarget()) { 290 return false; 291 } 292 if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) { 293 // Drag is limited to the current window. 294 if (mLocalWin != targetWin.mClient.asBinder()) { 295 return false; 296 } 297 } 298 299 return mCrossProfileCopyAllowed || 300 mSourceUserId == UserHandle.getUserId(targetWin.getOwningUid()); 301 } 302 303 /* helper - send a ACTION_DRAG_STARTED event only if the window has not 304 * previously been notified, i.e. it became visible after the drag operation 305 * was begun. This is a rare case. 306 */ 307 void sendDragStartedIfNeededLw(WindowState newWin) { 308 if (mDragInProgress) { 309 // If we have sent the drag-started, we needn't do so again 310 if (isWindowNotified(newWin)) { 311 return; 312 } 313 if (DEBUG_DRAG) { 314 Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin); 315 } 316 sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription); 317 } 318 } 319 320 private boolean isWindowNotified(WindowState newWin) { 321 for (WindowState ws : mNotifiedWindows) { 322 if (ws == newWin) { 323 return true; 324 } 325 } 326 return false; 327 } 328 329 private void broadcastDragEndedLw() { 330 final int myPid = Process.myPid(); 331 332 if (DEBUG_DRAG) { 333 Slog.d(TAG_WM, "broadcasting DRAG_ENDED"); 334 } 335 for (WindowState ws : mNotifiedWindows) { 336 float x = 0; 337 float y = 0; 338 if (!mDragResult && (ws.mSession.mPid == mPid)) { 339 // Report unconsumed drop location back to the app that started the drag. 340 x = mCurrentX; 341 y = mCurrentY; 342 } 343 DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, 344 x, y, null, null, null, null, mDragResult); 345 try { 346 ws.mClient.dispatchDragEvent(evt); 347 } catch (RemoteException e) { 348 Slog.w(TAG_WM, "Unable to drag-end window " + ws); 349 } 350 // if the current window is in the same process, 351 // the dispatch has already recycled the event 352 if (myPid != ws.mSession.mPid) { 353 evt.recycle(); 354 } 355 } 356 mNotifiedWindows.clear(); 357 mDragInProgress = false; 358 } 359 360 void endDragLw() { 361 if (mAnimation != null) { 362 return; 363 } 364 if (!mDragResult) { 365 mAnimation = createReturnAnimationLocked(); 366 mService.scheduleAnimationLocked(); 367 return; // Will call cleanUpDragLw when the animation is done. 368 } 369 cleanUpDragLw(); 370 } 371 372 void cancelDragLw() { 373 if (mAnimation != null) { 374 return; 375 } 376 mAnimation = createCancelAnimationLocked(); 377 mService.scheduleAnimationLocked(); 378 } 379 380 private void cleanUpDragLw() { 381 broadcastDragEndedLw(); 382 if (isFromSource(InputDevice.SOURCE_MOUSE)) { 383 mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY); 384 } 385 386 // stop intercepting input 387 unregister(); 388 389 // free our resources and drop all the object references 390 reset(); 391 mService.mDragState = null; 392 393 mService.mInputMonitor.updateInputWindowsLw(true /*force*/); 394 } 395 396 void notifyMoveLw(float x, float y) { 397 if (mAnimation != null) { 398 return; 399 } 400 mCurrentX = x; 401 mCurrentY = y; 402 403 // Move the surface to the given touch 404 if (SHOW_LIGHT_TRANSACTIONS) Slog.i( 405 TAG_WM, ">>> OPEN TRANSACTION notifyMoveLw"); 406 SurfaceControl.openTransaction(); 407 try { 408 mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY); 409 if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG " 410 + mSurfaceControl + ": pos=(" + 411 (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")"); 412 } finally { 413 SurfaceControl.closeTransaction(); 414 if (SHOW_LIGHT_TRANSACTIONS) Slog.i( 415 TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLw"); 416 } 417 notifyLocationLw(x, y); 418 } 419 420 void notifyLocationLw(float x, float y) { 421 // Tell the affected window 422 WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y); 423 if (touchedWin != null && !isWindowNotified(touchedWin)) { 424 // The drag point is over a window which was not notified about a drag start. 425 // Pretend it's over empty space. 426 touchedWin = null; 427 } 428 429 try { 430 final int myPid = Process.myPid(); 431 432 // have we dragged over a new window? 433 if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) { 434 if (DEBUG_DRAG) { 435 Slog.d(TAG_WM, "sending DRAG_EXITED to " + mTargetWindow); 436 } 437 // force DRAG_EXITED_EVENT if appropriate 438 DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED, 439 0, 0, null, null, null, null, false); 440 mTargetWindow.mClient.dispatchDragEvent(evt); 441 if (myPid != mTargetWindow.mSession.mPid) { 442 evt.recycle(); 443 } 444 } 445 if (touchedWin != null) { 446 if (false && DEBUG_DRAG) { 447 Slog.d(TAG_WM, "sending DRAG_LOCATION to " + touchedWin); 448 } 449 DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DRAG_LOCATION, 450 x, y, null, null, null, null, false); 451 touchedWin.mClient.dispatchDragEvent(evt); 452 if (myPid != touchedWin.mSession.mPid) { 453 evt.recycle(); 454 } 455 } 456 } catch (RemoteException e) { 457 Slog.w(TAG_WM, "can't send drag notification to windows"); 458 } 459 mTargetWindow = touchedWin; 460 } 461 462 // Find the drop target and tell it about the data. Returns 'true' if we can immediately 463 // dispatch the global drag-ended message, 'false' if we need to wait for a 464 // result from the recipient. 465 boolean notifyDropLw(float x, float y) { 466 if (mAnimation != null) { 467 return false; 468 } 469 mCurrentX = x; 470 mCurrentY = y; 471 472 WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y); 473 474 if (!isWindowNotified(touchedWin)) { 475 // "drop" outside a valid window -- no recipient to apply a 476 // timeout to, and we can send the drag-ended message immediately. 477 mDragResult = false; 478 return true; 479 } 480 481 if (DEBUG_DRAG) { 482 Slog.d(TAG_WM, "sending DROP to " + touchedWin); 483 } 484 485 final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid()); 486 487 DropPermissionsHandler dropPermissions = null; 488 if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && 489 (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) { 490 dropPermissions = new DropPermissionsHandler( 491 mData, 492 mUid, 493 touchedWin.getOwningPackage(), 494 mFlags & DRAG_FLAGS_URI_PERMISSIONS, 495 mSourceUserId, 496 targetUserId); 497 } 498 if (mSourceUserId != targetUserId){ 499 mData.fixUris(mSourceUserId); 500 } 501 final int myPid = Process.myPid(); 502 final IBinder token = touchedWin.mClient.asBinder(); 503 DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y, 504 null, null, mData, dropPermissions, false); 505 try { 506 touchedWin.mClient.dispatchDragEvent(evt); 507 508 // 5 second timeout for this window to respond to the drop 509 mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token); 510 Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token); 511 mService.mH.sendMessageDelayed(msg, 5000); 512 } catch (RemoteException e) { 513 Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin); 514 return true; 515 } finally { 516 if (myPid != touchedWin.mSession.mPid) { 517 evt.recycle(); 518 } 519 } 520 mToken = token; 521 return false; 522 } 523 524 private static DragEvent obtainDragEvent(WindowState win, int action, 525 float x, float y, Object localState, 526 ClipDescription description, ClipData data, 527 IDropPermissions dropPermissions, 528 boolean result) { 529 final float winX = win.translateToWindowX(x); 530 final float winY = win.translateToWindowY(y); 531 return DragEvent.obtain(action, winX, winY, localState, description, data, 532 dropPermissions, result); 533 } 534 535 boolean stepAnimationLocked(long currentTimeMs) { 536 if (mAnimation == null) { 537 return false; 538 } 539 540 mTransformation.clear(); 541 if (!mAnimation.getTransformation(currentTimeMs, mTransformation)) { 542 cleanUpDragLw(); 543 return false; 544 } 545 546 mTransformation.getMatrix().postTranslate( 547 mCurrentX - mThumbOffsetX, mCurrentY - mThumbOffsetY); 548 final float tmpFloats[] = mService.mTmpFloats; 549 mTransformation.getMatrix().getValues(tmpFloats); 550 mSurfaceControl.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]); 551 mSurfaceControl.setAlpha(mTransformation.getAlpha()); 552 mSurfaceControl.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y], 553 tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]); 554 return true; 555 } 556 557 private Animation createReturnAnimationLocked() { 558 final AnimationSet set = new AnimationSet(false); 559 set.addAnimation(new TranslateAnimation( 560 0, mOriginalX - mCurrentX, 0, mOriginalY - mCurrentY)); 561 set.addAnimation(new AlphaAnimation(mOriginalAlpha, mOriginalAlpha / 2)); 562 set.setDuration(ANIMATION_DURATION_MS); 563 set.setInterpolator(mCubicEaseOutInterpolator); 564 set.initialize(0, 0, 0, 0); 565 set.start(); // Will start on the first call to getTransformation. 566 return set; 567 } 568 569 private Animation createCancelAnimationLocked() { 570 final AnimationSet set = new AnimationSet(false); 571 set.addAnimation(new ScaleAnimation(1, 0, 1, 0, mThumbOffsetX, mThumbOffsetY)); 572 set.addAnimation(new AlphaAnimation(mOriginalAlpha, 0)); 573 set.setDuration(ANIMATION_DURATION_MS); 574 set.setInterpolator(mCubicEaseOutInterpolator); 575 set.initialize(0, 0, 0, 0); 576 set.start(); // Will start on the first call to getTransformation. 577 return set; 578 } 579 580 private boolean isFromSource(int source) { 581 return (mTouchSource & source) == source; 582 } 583 584 void overridePointerIconLw(int touchSource) { 585 mTouchSource = touchSource; 586 if (isFromSource(InputDevice.SOURCE_MOUSE)) { 587 InputManager.getInstance().setPointerIconShape(PointerIcon.STYLE_GRABBING); 588 } 589 } 590} 591