| /* |
| * Copyright (C) 2017 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 static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG; |
| import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; |
| import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; |
| |
| import android.annotation.NonNull; |
| import android.content.ClipData; |
| import android.os.Binder; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.util.Slog; |
| import android.view.Display; |
| import android.view.IWindow; |
| import android.view.SurfaceControl; |
| import android.view.SurfaceSession; |
| import android.view.View; |
| |
| import com.android.internal.util.Preconditions; |
| import com.android.server.wm.WindowManagerInternal.IDragDropCallback; |
| |
| import java.util.Objects; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** |
| * Managing drag and drop operations initiated by View#startDragAndDrop. |
| */ |
| class DragDropController { |
| private static final float DRAG_SHADOW_ALPHA_TRANSPARENT = .7071f; |
| private static final long DRAG_TIMEOUT_MS = 5000; |
| |
| // Messages for Handler. |
| static final int MSG_DRAG_END_TIMEOUT = 0; |
| static final int MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT = 1; |
| static final int MSG_ANIMATION_END = 2; |
| |
| /** |
| * Drag state per operation. |
| * Needs a lock of {@code WindowManagerService#mWindowMap} to read this. Needs both locks of |
| * {@code mWriteLock} and {@code WindowManagerService#mWindowMap} to update this. |
| * The variable is cleared by {@code #onDragStateClosedLocked} which is invoked by DragState |
| * itself, thus the variable can be null after calling DragState's methods. |
| */ |
| private DragState mDragState; |
| |
| private WindowManagerService mService; |
| private final Handler mHandler; |
| |
| /** |
| * Callback which is used to sync drag state with the vendor-specific code. |
| */ |
| @NonNull private AtomicReference<IDragDropCallback> mCallback = new AtomicReference<>( |
| new IDragDropCallback() {}); |
| |
| boolean dragDropActiveLocked() { |
| return mDragState != null && !mDragState.isClosing(); |
| } |
| |
| void registerCallback(IDragDropCallback callback) { |
| Objects.requireNonNull(callback); |
| mCallback.set(callback); |
| } |
| |
| DragDropController(WindowManagerService service, Looper looper) { |
| mService = service; |
| mHandler = new DragHandler(service, looper); |
| } |
| |
| void sendDragStartedIfNeededLocked(WindowState window) { |
| mDragState.sendDragStartedIfNeededLocked(window); |
| } |
| |
| IBinder performDrag(SurfaceSession session, int callerPid, int callerUid, IWindow window, |
| int flags, SurfaceControl surface, int touchSource, float touchX, float touchY, |
| float thumbCenterX, float thumbCenterY, ClipData data) { |
| if (DEBUG_DRAG) { |
| Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" + |
| Integer.toHexString(flags) + " data=" + data); |
| } |
| |
| final IBinder dragToken = new Binder(); |
| final boolean callbackResult = mCallback.get().prePerformDrag(window, dragToken, |
| touchSource, touchX, touchY, thumbCenterX, thumbCenterY, data); |
| try { |
| synchronized (mService.mGlobalLock) { |
| try { |
| if (!callbackResult) { |
| Slog.w(TAG_WM, "IDragDropCallback rejects the performDrag request"); |
| return null; |
| } |
| |
| if (dragDropActiveLocked()) { |
| Slog.w(TAG_WM, "Drag already in progress"); |
| return null; |
| } |
| |
| final WindowState callingWin = mService.windowForClientLocked( |
| null, window, false); |
| if (callingWin == null || callingWin.cantReceiveTouchInput()) { |
| Slog.w(TAG_WM, "Bad requesting window " + window); |
| return null; // !!! TODO: throw here? |
| } |
| |
| // !!! TODO: if input is not still focused on the initiating window, fail |
| // the drag initiation (e.g. an alarm window popped up just as the application |
| // called performDrag() |
| |
| // !!! TODO: extract the current touch (x, y) in screen coordinates. That |
| // will let us eliminate the (touchX,touchY) parameters from the API. |
| |
| // !!! FIXME: put all this heavy stuff onto the mHandler looper, as well as |
| // the actual drag event dispatch stuff in the dragstate |
| |
| // !!! TODO(multi-display): support other displays |
| |
| final DisplayContent displayContent = callingWin.getDisplayContent(); |
| if (displayContent == null) { |
| Slog.w(TAG_WM, "display content is null"); |
| return null; |
| } |
| |
| final float alpha = (flags & View.DRAG_FLAG_OPAQUE) == 0 ? |
| DRAG_SHADOW_ALPHA_TRANSPARENT : 1; |
| final IBinder winBinder = window.asBinder(); |
| IBinder token = new Binder(); |
| mDragState = new DragState(mService, this, token, surface, flags, winBinder); |
| surface = null; |
| mDragState.mPid = callerPid; |
| mDragState.mUid = callerUid; |
| mDragState.mOriginalAlpha = alpha; |
| mDragState.mToken = dragToken; |
| mDragState.mDisplayContent = displayContent; |
| |
| final Display display = displayContent.getDisplay(); |
| if (!mCallback.get().registerInputChannel( |
| mDragState, display, mService.mInputManager, |
| callingWin.mInputChannel)) { |
| Slog.e(TAG_WM, "Unable to transfer touch focus"); |
| return null; |
| } |
| |
| mDragState.mData = data; |
| mDragState.broadcastDragStartedLocked(touchX, touchY); |
| mDragState.overridePointerIconLocked(touchSource); |
| // remember the thumb offsets for later |
| mDragState.mThumbOffsetX = thumbCenterX; |
| mDragState.mThumbOffsetY = thumbCenterY; |
| |
| // Make the surface visible at the proper location |
| final SurfaceControl surfaceControl = mDragState.mSurfaceControl; |
| if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG_WM, ">>> OPEN TRANSACTION performDrag"); |
| |
| final SurfaceControl.Transaction transaction = mDragState.mTransaction; |
| transaction.setAlpha(surfaceControl, mDragState.mOriginalAlpha); |
| transaction.setPosition( |
| surfaceControl, touchX - thumbCenterX, touchY - thumbCenterY); |
| transaction.show(surfaceControl); |
| displayContent.reparentToOverlay(transaction, surfaceControl); |
| callingWin.scheduleAnimation(); |
| |
| if (SHOW_LIGHT_TRANSACTIONS) { |
| Slog.i(TAG_WM, "<<< CLOSE TRANSACTION performDrag"); |
| } |
| |
| mDragState.notifyLocationLocked(touchX, touchY); |
| } finally { |
| if (surface != null) { |
| surface.release(); |
| } |
| if (mDragState != null && !mDragState.isInProgress()) { |
| mDragState.closeLocked(); |
| } |
| } |
| } |
| return dragToken; // success! |
| } finally { |
| mCallback.get().postPerformDrag(); |
| } |
| } |
| |
| void reportDropResult(IWindow window, boolean consumed) { |
| IBinder token = window.asBinder(); |
| if (DEBUG_DRAG) { |
| Slog.d(TAG_WM, "Drop result=" + consumed + " reported by " + token); |
| } |
| |
| mCallback.get().preReportDropResult(window, consumed); |
| try { |
| synchronized (mService.mGlobalLock) { |
| if (mDragState == null) { |
| // Most likely the drop recipient ANRed and we ended the drag |
| // out from under it. Log the issue and move on. |
| Slog.w(TAG_WM, "Drop result given but no drag in progress"); |
| return; |
| } |
| |
| if (mDragState.mToken != token) { |
| // We're in a drag, but the wrong window has responded. |
| Slog.w(TAG_WM, "Invalid drop-result claim by " + window); |
| throw new IllegalStateException("reportDropResult() by non-recipient"); |
| } |
| |
| // The right window has responded, even if it's no longer around, |
| // so be sure to halt the timeout even if the later WindowState |
| // lookup fails. |
| mHandler.removeMessages(MSG_DRAG_END_TIMEOUT, window.asBinder()); |
| WindowState callingWin = mService.windowForClientLocked(null, window, false); |
| if (callingWin == null) { |
| Slog.w(TAG_WM, "Bad result-reporting window " + window); |
| return; // !!! TODO: throw here? |
| } |
| |
| mDragState.mDragResult = consumed; |
| mDragState.endDragLocked(); |
| } |
| } finally { |
| mCallback.get().postReportDropResult(); |
| } |
| } |
| |
| void cancelDragAndDrop(IBinder dragToken, boolean skipAnimation) { |
| if (DEBUG_DRAG) { |
| Slog.d(TAG_WM, "cancelDragAndDrop"); |
| } |
| |
| mCallback.get().preCancelDragAndDrop(dragToken); |
| try { |
| synchronized (mService.mGlobalLock) { |
| if (mDragState == null) { |
| Slog.w(TAG_WM, "cancelDragAndDrop() without prepareDrag()"); |
| throw new IllegalStateException("cancelDragAndDrop() without prepareDrag()"); |
| } |
| |
| if (mDragState.mToken != dragToken) { |
| Slog.w(TAG_WM, |
| "cancelDragAndDrop() does not match prepareDrag()"); |
| throw new IllegalStateException( |
| "cancelDragAndDrop() does not match prepareDrag()"); |
| } |
| |
| mDragState.mDragResult = false; |
| mDragState.cancelDragLocked(skipAnimation); |
| } |
| } finally { |
| mCallback.get().postCancelDragAndDrop(); |
| } |
| } |
| |
| /** |
| * Handles motion events. |
| * @param keepHandling Whether if the drag operation is continuing or this is the last motion |
| * event. |
| * @param newX X coordinate value in dp in the screen coordinate |
| * @param newY Y coordinate value in dp in the screen coordinate |
| */ |
| void handleMotionEvent(boolean keepHandling, float newX, float newY) { |
| synchronized (mService.mGlobalLock) { |
| if (!dragDropActiveLocked()) { |
| // The drag has ended but the clean-up message has not been processed by |
| // window manager. Drop events that occur after this until window manager |
| // has a chance to clean-up the input handle. |
| return; |
| } |
| |
| if (keepHandling) { |
| mDragState.notifyMoveLocked(newX, newY); |
| } else { |
| mDragState.notifyDropLocked(newX, newY); |
| } |
| } |
| } |
| |
| void dragRecipientEntered(IWindow window) { |
| if (DEBUG_DRAG) { |
| Slog.d(TAG_WM, "Drag into new candidate view @ " + window.asBinder()); |
| } |
| } |
| |
| void dragRecipientExited(IWindow window) { |
| if (DEBUG_DRAG) { |
| Slog.d(TAG_WM, "Drag from old candidate view @ " + window.asBinder()); |
| } |
| } |
| |
| /** |
| * Sends a message to the Handler managed by DragDropController. |
| */ |
| void sendHandlerMessage(int what, Object arg) { |
| mHandler.obtainMessage(what, arg).sendToTarget(); |
| } |
| |
| /** |
| * Sends a timeout message to the Handler managed by DragDropController. |
| */ |
| void sendTimeoutMessage(int what, Object arg) { |
| mHandler.removeMessages(what, arg); |
| final Message msg = mHandler.obtainMessage(what, arg); |
| mHandler.sendMessageDelayed(msg, DRAG_TIMEOUT_MS); |
| } |
| |
| /** |
| * Notifies the current drag state is closed. |
| */ |
| void onDragStateClosedLocked(DragState dragState) { |
| if (mDragState != dragState) { |
| Slog.wtf(TAG_WM, "Unknown drag state is closed"); |
| return; |
| } |
| mDragState = null; |
| } |
| |
| private class DragHandler extends Handler { |
| /** |
| * Lock for window manager. |
| */ |
| private final WindowManagerService mService; |
| |
| DragHandler(WindowManagerService service, Looper looper) { |
| super(looper); |
| mService = service; |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_DRAG_END_TIMEOUT: { |
| final IBinder win = (IBinder) msg.obj; |
| if (DEBUG_DRAG) { |
| Slog.w(TAG_WM, "Timeout ending drag to win " + win); |
| } |
| |
| synchronized (mService.mGlobalLock) { |
| // !!! TODO: ANR the drag-receiving app |
| if (mDragState != null) { |
| mDragState.mDragResult = false; |
| mDragState.endDragLocked(); |
| } |
| } |
| break; |
| } |
| |
| case MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT: { |
| if (DEBUG_DRAG) |
| Slog.d(TAG_WM, "Drag ending; tearing down input channel"); |
| final DragState.InputInterceptor interceptor = |
| (DragState.InputInterceptor) msg.obj; |
| if (interceptor == null) return; |
| synchronized (mService.mGlobalLock) { |
| interceptor.tearDown(); |
| } |
| break; |
| } |
| |
| case MSG_ANIMATION_END: { |
| synchronized (mService.mGlobalLock) { |
| if (mDragState == null) { |
| Slog.wtf(TAG_WM, "mDragState unexpectedly became null while " + |
| "plyaing animation"); |
| return; |
| } |
| mDragState.closeLocked(); |
| } |
| break; |
| } |
| } |
| } |
| } |
| } |