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