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