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