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