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