/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.wm; import com.android.server.input.InputApplicationHandle; import com.android.server.input.InputWindowHandle; import com.android.server.wm.WindowManagerService.DragInputEventReceiver; import com.android.server.wm.WindowManagerService.H; import android.content.ClipData; import android.content.ClipDescription; import android.graphics.Point; import android.graphics.Region; import android.os.IBinder; import android.os.Message; import android.os.Process; import android.os.RemoteException; import android.util.Slog; import android.view.Display; import android.view.DragEvent; import android.view.InputChannel; import android.view.Surface; import android.view.SurfaceControl; import android.view.View; import android.view.WindowManager; import java.util.ArrayList; /** * Drag/drop state */ class DragState { final WindowManagerService mService; IBinder mToken; SurfaceControl mSurfaceControl; int mFlags; IBinder mLocalWin; ClipData mData; ClipDescription mDataDescription; boolean mDragResult; float mCurrentX, mCurrentY; float mThumbOffsetX, mThumbOffsetY; InputChannel mServerChannel, mClientChannel; DragInputEventReceiver mInputEventReceiver; InputApplicationHandle mDragApplicationHandle; InputWindowHandle mDragWindowHandle; WindowState mTargetWindow; ArrayList mNotifiedWindows; boolean mDragInProgress; Display mDisplay; private final Region mTmpRegion = new Region(); DragState(WindowManagerService service, IBinder token, SurfaceControl surface, int flags, IBinder localWin) { mService = service; mToken = token; mSurfaceControl = surface; mFlags = flags; mLocalWin = localWin; mNotifiedWindows = new ArrayList(); } void reset() { if (mSurfaceControl != null) { mSurfaceControl.destroy(); } mSurfaceControl = null; mFlags = 0; mLocalWin = null; mToken = null; mData = null; mThumbOffsetX = mThumbOffsetY = 0; mNotifiedWindows = null; } /** * @param display The Display that the window being dragged is on. */ void register(Display display) { mDisplay = display; if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "registering drag input channel"); if (mClientChannel != null) { Slog.e(WindowManagerService.TAG, "Duplicate register of drag input channel"); } else { InputChannel[] channels = InputChannel.openInputChannelPair("drag"); mServerChannel = channels[0]; mClientChannel = channels[1]; mService.mInputManager.registerInputChannel(mServerChannel, null); mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel, mService.mH.getLooper()); mDragApplicationHandle = new InputApplicationHandle(null); mDragApplicationHandle.name = "drag"; mDragApplicationHandle.dispatchingTimeoutNanos = WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, mDisplay.getDisplayId()); mDragWindowHandle.name = "drag"; mDragWindowHandle.inputChannel = mServerChannel; mDragWindowHandle.layer = getDragLayerLw(); mDragWindowHandle.layoutParamsFlags = 0; mDragWindowHandle.layoutParamsPrivateFlags = 0; mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; mDragWindowHandle.dispatchingTimeoutNanos = WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; mDragWindowHandle.visible = true; mDragWindowHandle.canReceiveKeys = false; mDragWindowHandle.hasFocus = true; mDragWindowHandle.hasWallpaper = false; mDragWindowHandle.paused = false; mDragWindowHandle.ownerPid = Process.myPid(); mDragWindowHandle.ownerUid = Process.myUid(); mDragWindowHandle.inputFeatures = 0; mDragWindowHandle.scaleFactor = 1.0f; // The drag window cannot receive new touches. mDragWindowHandle.touchableRegion.setEmpty(); // The drag window covers the entire display mDragWindowHandle.frameLeft = 0; mDragWindowHandle.frameTop = 0; Point p = new Point(); mDisplay.getRealSize(p); mDragWindowHandle.frameRight = p.x; mDragWindowHandle.frameBottom = p.y; // Pause rotations before a drag. if (WindowManagerService.DEBUG_ORIENTATION) { Slog.d(WindowManagerService.TAG, "Pausing rotation during drag"); } mService.pauseRotationLocked(); } } void unregister() { if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "unregistering drag input channel"); if (mClientChannel == null) { Slog.e(WindowManagerService.TAG, "Unregister of nonexistent drag input channel"); } else { mService.mInputManager.unregisterInputChannel(mServerChannel); mInputEventReceiver.dispose(); mInputEventReceiver = null; mClientChannel.dispose(); mServerChannel.dispose(); mClientChannel = null; mServerChannel = null; mDragWindowHandle = null; mDragApplicationHandle = null; // Resume rotations after a drag. if (WindowManagerService.DEBUG_ORIENTATION) { Slog.d(WindowManagerService.TAG, "Resuming rotation after drag"); } mService.resumeRotationLocked(); } } int getDragLayerLw() { return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_DRAG) * WindowManagerService.TYPE_LAYER_MULTIPLIER + WindowManagerService.TYPE_LAYER_OFFSET; } /* call out to each visible window/session informing it about the drag */ void broadcastDragStartedLw(final float touchX, final float touchY) { // Cache a base-class instance of the clip metadata so that parceling // works correctly in calling out to the apps. mDataDescription = (mData != null) ? mData.getDescription() : null; mNotifiedWindows.clear(); mDragInProgress = true; if (WindowManagerService.DEBUG_DRAG) { Slog.d(WindowManagerService.TAG, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); } final WindowList windows = mService.getWindowListLocked(mDisplay); if (windows != null) { final int N = windows.size(); for (int i = 0; i < N; i++) { sendDragStartedLw(windows.get(i), touchX, touchY, mDataDescription); } } } /* helper - send a caller-provided event, presumed to be DRAG_STARTED, if the * designated window is potentially a drop recipient. There are race situations * around DRAG_ENDED broadcast, so we make sure that once we've declared that * the drag has ended, we never send out another DRAG_STARTED for this drag action. * * This method clones the 'event' parameter if it's being delivered to the same * process, so it's safe for the caller to call recycle() on the event afterwards. */ private void sendDragStartedLw(WindowState newWin, float touchX, float touchY, ClipDescription desc) { // Don't actually send the event if the drag is supposed to be pinned // to the originating window but 'newWin' is not that window. if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) { final IBinder winBinder = newWin.mClient.asBinder(); if (winBinder != mLocalWin) { if (WindowManagerService.DEBUG_DRAG) { Slog.d(WindowManagerService.TAG, "Not dispatching local DRAG_STARTED to " + newWin); } return; } } if (mDragInProgress && newWin.isPotentialDragTarget()) { DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED, touchX, touchY, null, desc, null, false); try { newWin.mClient.dispatchDragEvent(event); // track each window that we've notified that the drag is starting mNotifiedWindows.add(newWin); } catch (RemoteException e) { Slog.w(WindowManagerService.TAG, "Unable to drag-start window " + newWin); } finally { // if the callee was local, the dispatch has already recycled the event if (Process.myPid() != newWin.mSession.mPid) { event.recycle(); } } } } /* helper - construct and send a DRAG_STARTED event only if the window has not * previously been notified, i.e. it became visible after the drag operation * was begun. This is a rare case. */ void sendDragStartedIfNeededLw(WindowState newWin) { if (mDragInProgress) { // If we have sent the drag-started, we needn't do so again for (WindowState ws : mNotifiedWindows) { if (ws == newWin) { return; } } if (WindowManagerService.DEBUG_DRAG) { Slog.d(WindowManagerService.TAG, "need to send DRAG_STARTED to new window " + newWin); } sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription); } } void broadcastDragEndedLw() { if (WindowManagerService.DEBUG_DRAG) { Slog.d(WindowManagerService.TAG, "broadcasting DRAG_ENDED"); } DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, 0, 0, null, null, null, mDragResult); for (WindowState ws: mNotifiedWindows) { try { ws.mClient.dispatchDragEvent(evt); } catch (RemoteException e) { Slog.w(WindowManagerService.TAG, "Unable to drag-end window " + ws); } } mNotifiedWindows.clear(); mDragInProgress = false; evt.recycle(); } void endDragLw() { mService.mDragState.broadcastDragEndedLw(); // stop intercepting input mService.mDragState.unregister(); mService.mInputMonitor.updateInputWindowsLw(true /*force*/); // free our resources and drop all the object references mService.mDragState.reset(); mService.mDragState = null; } void notifyMoveLw(float x, float y) { final int myPid = Process.myPid(); // Move the surface to the given touch if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i( WindowManagerService.TAG, ">>> OPEN TRANSACTION notifyMoveLw"); SurfaceControl.openTransaction(); try { mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY); if (WindowManagerService.SHOW_TRANSACTIONS) Slog.i(WindowManagerService.TAG, " DRAG " + mSurfaceControl + ": pos=(" + (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")"); } finally { SurfaceControl.closeTransaction(); if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS) Slog.i( WindowManagerService.TAG, "<<< CLOSE TRANSACTION notifyMoveLw"); } // Tell the affected window WindowState touchedWin = getTouchedWinAtPointLw(x, y); if (touchedWin == null) { if (WindowManagerService.DEBUG_DRAG) Slog.d(WindowManagerService.TAG, "No touched win at x=" + x + " y=" + y); return; } if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0) { final IBinder touchedBinder = touchedWin.mClient.asBinder(); if (touchedBinder != mLocalWin) { // This drag is pinned only to the originating window, but the drag // point is outside that window. Pretend it's over empty space. touchedWin = null; } } try { // have we dragged over a new window? if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) { if (WindowManagerService.DEBUG_DRAG) { Slog.d(WindowManagerService.TAG, "sending DRAG_EXITED to " + mTargetWindow); } // force DRAG_EXITED_EVENT if appropriate DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED, x, y, null, null, null, false); mTargetWindow.mClient.dispatchDragEvent(evt); if (myPid != mTargetWindow.mSession.mPid) { evt.recycle(); } } if (touchedWin != null) { if (false && WindowManagerService.DEBUG_DRAG) { Slog.d(WindowManagerService.TAG, "sending DRAG_LOCATION to " + touchedWin); } DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DRAG_LOCATION, x, y, null, null, null, false); touchedWin.mClient.dispatchDragEvent(evt); if (myPid != touchedWin.mSession.mPid) { evt.recycle(); } } } catch (RemoteException e) { Slog.w(WindowManagerService.TAG, "can't send drag notification to windows"); } mTargetWindow = touchedWin; } // Tell the drop target about the data. Returns 'true' if we can immediately // dispatch the global drag-ended message, 'false' if we need to wait for a // result from the recipient. boolean notifyDropLw(float x, float y) { WindowState touchedWin = getTouchedWinAtPointLw(x, y); if (touchedWin == null) { // "drop" outside a valid window -- no recipient to apply a // timeout to, and we can send the drag-ended message immediately. mDragResult = false; return true; } if (WindowManagerService.DEBUG_DRAG) { Slog.d(WindowManagerService.TAG, "sending DROP to " + touchedWin); } final int myPid = Process.myPid(); final IBinder token = touchedWin.mClient.asBinder(); DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y, null, null, mData, false); try { touchedWin.mClient.dispatchDragEvent(evt); // 5 second timeout for this window to respond to the drop mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token); Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token); mService.mH.sendMessageDelayed(msg, 5000); } catch (RemoteException e) { Slog.w(WindowManagerService.TAG, "can't send drop notification to win " + touchedWin); return true; } finally { if (myPid != touchedWin.mSession.mPid) { evt.recycle(); } } mToken = token; return false; } // Find the visible, touch-deliverable window under the given point private WindowState getTouchedWinAtPointLw(float xf, float yf) { WindowState touchedWin = null; final int x = (int) xf; final int y = (int) yf; final WindowList windows = mService.getWindowListLocked(mDisplay); if (windows == null) { return null; } final int N = windows.size(); for (int i = N - 1; i >= 0; i--) { WindowState child = windows.get(i); final int flags = child.mAttrs.flags; if (!child.isVisibleLw()) { // not visible == don't tell about drags continue; } if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) { // not touchable == don't tell about drags continue; } child.getTouchableRegion(mTmpRegion); final int touchFlags = flags & (WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL); if (mTmpRegion.contains(x, y) || touchFlags == 0) { // Found it touchedWin = child; break; } } return touchedWin; } private static DragEvent obtainDragEvent(WindowState win, int action, float x, float y, Object localState, ClipDescription description, ClipData data, boolean result) { float winX = x - win.mFrame.left; float winY = y - win.mFrame.top; if (win.mEnforceSizeCompat) { winX *= win.mGlobalScale; winY *= win.mGlobalScale; } return DragEvent.obtain(action, winX, winY, localState, description, data, result); } }