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