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