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