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
17package com.android.server.wm;
18
19import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
20import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION;
21import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
22import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
23import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
24
25import android.content.ClipData;
26import android.content.ClipDescription;
27import android.content.Context;
28import android.graphics.Matrix;
29import android.graphics.Point;
30import android.hardware.input.InputManager;
31import android.os.Build;
32import android.os.IBinder;
33import android.os.Message;
34import android.os.Process;
35import android.os.RemoteException;
36import android.os.ServiceManager;
37import android.os.UserHandle;
38import android.os.UserManager;
39import android.os.IUserManager;
40import android.util.Slog;
41import android.view.Display;
42import android.view.DragEvent;
43import android.view.InputChannel;
44import android.view.InputDevice;
45import android.view.PointerIcon;
46import android.view.SurfaceControl;
47import android.view.View;
48import android.view.WindowManager;
49import android.view.animation.AlphaAnimation;
50import android.view.animation.Animation;
51import android.view.animation.AnimationSet;
52import android.view.animation.DecelerateInterpolator;
53import android.view.animation.Interpolator;
54import android.view.animation.ScaleAnimation;
55import android.view.animation.Transformation;
56import android.view.animation.TranslateAnimation;
57
58import com.android.server.input.InputApplicationHandle;
59import com.android.server.input.InputWindowHandle;
60import com.android.server.wm.WindowManagerService.DragInputEventReceiver;
61import com.android.server.wm.WindowManagerService.H;
62
63import com.android.internal.view.IDragAndDropPermissions;
64
65import java.util.ArrayList;
66
67/**
68 * Drag/drop state
69 */
70class DragState {
71    private static final long MIN_ANIMATION_DURATION_MS = 195;
72    private static final long MAX_ANIMATION_DURATION_MS = 375;
73
74    private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ |
75            View.DRAG_FLAG_GLOBAL_URI_WRITE;
76
77    private static final int DRAG_FLAGS_URI_PERMISSIONS = DRAG_FLAGS_URI_ACCESS |
78            View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION |
79            View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION;
80
81    final WindowManagerService mService;
82    IBinder mToken;
83    SurfaceControl mSurfaceControl;
84    int mFlags;
85    IBinder mLocalWin;
86    int mPid;
87    int mUid;
88    int mSourceUserId;
89    boolean mCrossProfileCopyAllowed;
90    ClipData mData;
91    ClipDescription mDataDescription;
92    int mTouchSource;
93    boolean mDragResult;
94    float mOriginalAlpha;
95    float mOriginalX, mOriginalY;
96    float mCurrentX, mCurrentY;
97    float mThumbOffsetX, mThumbOffsetY;
98    InputInterceptor mInputInterceptor;
99    WindowState mTargetWindow;
100    ArrayList<WindowState> mNotifiedWindows;
101    boolean mDragInProgress;
102    DisplayContent mDisplayContent;
103
104    private Animation mAnimation;
105    final Transformation mTransformation = new Transformation();
106    private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f);
107    private Point mDisplaySize = new Point();
108
109    DragState(WindowManagerService service, IBinder token, SurfaceControl surface,
110            int flags, IBinder localWin) {
111        mService = service;
112        mToken = token;
113        mSurfaceControl = surface;
114        mFlags = flags;
115        mLocalWin = localWin;
116        mNotifiedWindows = new ArrayList<WindowState>();
117    }
118
119    void reset() {
120        if (mSurfaceControl != null) {
121            mSurfaceControl.destroy();
122        }
123        mSurfaceControl = null;
124        mFlags = 0;
125        mLocalWin = null;
126        mToken = null;
127        mData = null;
128        mThumbOffsetX = mThumbOffsetY = 0;
129        mNotifiedWindows = null;
130    }
131
132    class InputInterceptor {
133        InputChannel mServerChannel, mClientChannel;
134        DragInputEventReceiver mInputEventReceiver;
135        InputApplicationHandle mDragApplicationHandle;
136        InputWindowHandle mDragWindowHandle;
137
138        InputInterceptor(Display display) {
139            InputChannel[] channels = InputChannel.openInputChannelPair("drag");
140            mServerChannel = channels[0];
141            mClientChannel = channels[1];
142            mService.mInputManager.registerInputChannel(mServerChannel, null);
143            mInputEventReceiver = mService.new DragInputEventReceiver(mClientChannel,
144                    mService.mH.getLooper());
145
146            mDragApplicationHandle = new InputApplicationHandle(null);
147            mDragApplicationHandle.name = "drag";
148            mDragApplicationHandle.dispatchingTimeoutNanos =
149                    WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
150
151            mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null,
152                    display.getDisplayId());
153            mDragWindowHandle.name = "drag";
154            mDragWindowHandle.inputChannel = mServerChannel;
155            mDragWindowHandle.layer = getDragLayerLw();
156            mDragWindowHandle.layoutParamsFlags = 0;
157            mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
158            mDragWindowHandle.dispatchingTimeoutNanos =
159                    WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS;
160            mDragWindowHandle.visible = true;
161            mDragWindowHandle.canReceiveKeys = false;
162            mDragWindowHandle.hasFocus = true;
163            mDragWindowHandle.hasWallpaper = false;
164            mDragWindowHandle.paused = false;
165            mDragWindowHandle.ownerPid = Process.myPid();
166            mDragWindowHandle.ownerUid = Process.myUid();
167            mDragWindowHandle.inputFeatures = 0;
168            mDragWindowHandle.scaleFactor = 1.0f;
169
170            // The drag window cannot receive new touches.
171            mDragWindowHandle.touchableRegion.setEmpty();
172
173            // The drag window covers the entire display
174            mDragWindowHandle.frameLeft = 0;
175            mDragWindowHandle.frameTop = 0;
176            mDragWindowHandle.frameRight = mDisplaySize.x;
177            mDragWindowHandle.frameBottom = mDisplaySize.y;
178
179            // Pause rotations before a drag.
180            if (DEBUG_ORIENTATION) {
181                Slog.d(TAG_WM, "Pausing rotation during drag");
182            }
183            mService.pauseRotationLocked();
184        }
185
186        void tearDown() {
187            mService.mInputManager.unregisterInputChannel(mServerChannel);
188            mInputEventReceiver.dispose();
189            mInputEventReceiver = null;
190            mClientChannel.dispose();
191            mServerChannel.dispose();
192            mClientChannel = null;
193            mServerChannel = null;
194
195            mDragWindowHandle = null;
196            mDragApplicationHandle = null;
197
198            // Resume rotations after a drag.
199            if (DEBUG_ORIENTATION) {
200                Slog.d(TAG_WM, "Resuming rotation after drag");
201            }
202            mService.resumeRotationLocked();
203        }
204    }
205
206    InputChannel getInputChannel() {
207        return mInputInterceptor == null ? null : mInputInterceptor.mServerChannel;
208    }
209
210    InputWindowHandle getInputWindowHandle() {
211        return mInputInterceptor == null ? null : mInputInterceptor.mDragWindowHandle;
212    }
213
214    /**
215     * @param display The Display that the window being dragged is on.
216     */
217    void register(Display display) {
218        display.getRealSize(mDisplaySize);
219        if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel");
220        if (mInputInterceptor != null) {
221            Slog.e(TAG_WM, "Duplicate register of drag input channel");
222        } else {
223            mInputInterceptor = new InputInterceptor(display);
224            mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
225        }
226    }
227
228    void unregister() {
229        if (DEBUG_DRAG) Slog.d(TAG_WM, "unregistering drag input channel");
230        if (mInputInterceptor == null) {
231            Slog.e(TAG_WM, "Unregister of nonexistent drag input channel");
232        } else {
233            // Input channel should be disposed on the thread where the input is being handled.
234            mService.mH.obtainMessage(
235                    H.TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor).sendToTarget();
236            mInputInterceptor = null;
237            mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
238        }
239    }
240
241    int getDragLayerLw() {
242        return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_DRAG)
243                * WindowManagerService.TYPE_LAYER_MULTIPLIER
244                + WindowManagerService.TYPE_LAYER_OFFSET;
245    }
246
247    /* call out to each visible window/session informing it about the drag
248     */
249    void broadcastDragStartedLw(final float touchX, final float touchY) {
250        mOriginalX = mCurrentX = touchX;
251        mOriginalY = mCurrentY = touchY;
252
253        // Cache a base-class instance of the clip metadata so that parceling
254        // works correctly in calling out to the apps.
255        mDataDescription = (mData != null) ? mData.getDescription() : null;
256        mNotifiedWindows.clear();
257        mDragInProgress = true;
258
259        mSourceUserId = UserHandle.getUserId(mUid);
260
261        final IUserManager userManager =
262                (IUserManager) ServiceManager.getService(Context.USER_SERVICE);
263        try {
264            mCrossProfileCopyAllowed = !userManager.getUserRestrictions(mSourceUserId).getBoolean(
265                    UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
266        } catch (RemoteException e) {
267            Slog.e(TAG_WM, "Remote Exception calling UserManager: " + e);
268            mCrossProfileCopyAllowed = false;
269        }
270
271        if (DEBUG_DRAG) {
272            Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")");
273        }
274
275        mDisplayContent.forAllWindows(w -> {
276            sendDragStartedLw(w, touchX, touchY, mDataDescription);
277        }, false /* traverseTopToBottom */ );
278    }
279
280    /* helper - send a ACTION_DRAG_STARTED event, if the
281     * designated window is potentially a drop recipient.  There are race situations
282     * around DRAG_ENDED broadcast, so we make sure that once we've declared that
283     * the drag has ended, we never send out another DRAG_STARTED for this drag action.
284     *
285     * This method clones the 'event' parameter if it's being delivered to the same
286     * process, so it's safe for the caller to call recycle() on the event afterwards.
287     */
288    private void sendDragStartedLw(WindowState newWin, float touchX, float touchY,
289            ClipDescription desc) {
290        if (mDragInProgress && isValidDropTarget(newWin)) {
291            DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED,
292                    touchX, touchY, null, desc, null, null, false);
293            try {
294                newWin.mClient.dispatchDragEvent(event);
295                // track each window that we've notified that the drag is starting
296                mNotifiedWindows.add(newWin);
297            } catch (RemoteException e) {
298                Slog.w(TAG_WM, "Unable to drag-start window " + newWin);
299            } finally {
300                // if the callee was local, the dispatch has already recycled the event
301                if (Process.myPid() != newWin.mSession.mPid) {
302                    event.recycle();
303                }
304            }
305        }
306    }
307
308    private boolean isValidDropTarget(WindowState targetWin) {
309        if (targetWin == null) {
310            return false;
311        }
312        if (!targetWin.isPotentialDragTarget()) {
313            return false;
314        }
315        if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0 || !targetWindowSupportsGlobalDrag(targetWin)) {
316            // Drag is limited to the current window.
317            if (mLocalWin != targetWin.mClient.asBinder()) {
318                return false;
319            }
320        }
321
322        return mCrossProfileCopyAllowed ||
323                mSourceUserId == UserHandle.getUserId(targetWin.getOwningUid());
324    }
325
326    private boolean targetWindowSupportsGlobalDrag(WindowState targetWin) {
327        // Global drags are limited to system windows, and windows for apps that are targeting N and
328        // above.
329        return targetWin.mAppToken == null
330                || targetWin.mAppToken.mTargetSdk >= Build.VERSION_CODES.N;
331    }
332
333    /* helper - send a ACTION_DRAG_STARTED event only if the window has not
334     * previously been notified, i.e. it became visible after the drag operation
335     * was begun.  This is a rare case.
336     */
337    void sendDragStartedIfNeededLw(WindowState newWin) {
338        if (mDragInProgress) {
339            // If we have sent the drag-started, we needn't do so again
340            if (isWindowNotified(newWin)) {
341                return;
342            }
343            if (DEBUG_DRAG) {
344                Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin);
345            }
346            sendDragStartedLw(newWin, mCurrentX, mCurrentY, mDataDescription);
347        }
348    }
349
350    private boolean isWindowNotified(WindowState newWin) {
351        for (WindowState ws : mNotifiedWindows) {
352            if (ws == newWin) {
353                return true;
354            }
355        }
356        return false;
357    }
358
359    private void broadcastDragEndedLw() {
360        final int myPid = Process.myPid();
361
362        if (DEBUG_DRAG) {
363            Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
364        }
365        for (WindowState ws : mNotifiedWindows) {
366            float x = 0;
367            float y = 0;
368            if (!mDragResult && (ws.mSession.mPid == mPid)) {
369                // Report unconsumed drop location back to the app that started the drag.
370                x = mCurrentX;
371                y = mCurrentY;
372            }
373            DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED,
374                    x, y, null, null, null, null, mDragResult);
375            try {
376                ws.mClient.dispatchDragEvent(evt);
377            } catch (RemoteException e) {
378                Slog.w(TAG_WM, "Unable to drag-end window " + ws);
379            }
380            // if the current window is in the same process,
381            // the dispatch has already recycled the event
382            if (myPid != ws.mSession.mPid) {
383                evt.recycle();
384            }
385        }
386        mNotifiedWindows.clear();
387        mDragInProgress = false;
388    }
389
390    void endDragLw() {
391        if (mAnimation != null) {
392            return;
393        }
394        if (!mDragResult) {
395            mAnimation = createReturnAnimationLocked();
396            mService.scheduleAnimationLocked();
397            return;  // Will call cleanUpDragLw when the animation is done.
398        }
399        cleanUpDragLw();
400    }
401
402    void cancelDragLw() {
403        if (mAnimation != null) {
404            return;
405        }
406        mAnimation = createCancelAnimationLocked();
407        mService.scheduleAnimationLocked();
408    }
409
410    private void cleanUpDragLw() {
411        broadcastDragEndedLw();
412        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
413            mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY);
414        }
415
416        // stop intercepting input
417        unregister();
418
419        // free our resources and drop all the object references
420        reset();
421        mService.mDragState = null;
422    }
423
424    void notifyMoveLw(float x, float y) {
425        if (mAnimation != null) {
426            return;
427        }
428        mCurrentX = x;
429        mCurrentY = y;
430
431        // Move the surface to the given touch
432        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
433                TAG_WM, ">>> OPEN TRANSACTION notifyMoveLw");
434        mService.openSurfaceTransaction();
435        try {
436            mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY);
437            if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, "  DRAG "
438                    + mSurfaceControl + ": pos=(" +
439                    (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")");
440        } finally {
441            mService.closeSurfaceTransaction();
442            if (SHOW_LIGHT_TRANSACTIONS) Slog.i(
443                    TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLw");
444        }
445        notifyLocationLw(x, y);
446    }
447
448    void notifyLocationLw(float x, float y) {
449        // Tell the affected window
450        WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
451        if (touchedWin != null && !isWindowNotified(touchedWin)) {
452            // The drag point is over a window which was not notified about a drag start.
453            // Pretend it's over empty space.
454            touchedWin = null;
455        }
456
457        try {
458            final int myPid = Process.myPid();
459
460            // have we dragged over a new window?
461            if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) {
462                if (DEBUG_DRAG) {
463                    Slog.d(TAG_WM, "sending DRAG_EXITED to " + mTargetWindow);
464                }
465                // force DRAG_EXITED_EVENT if appropriate
466                DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED,
467                        0, 0, null, null, null, null, false);
468                mTargetWindow.mClient.dispatchDragEvent(evt);
469                if (myPid != mTargetWindow.mSession.mPid) {
470                    evt.recycle();
471                }
472            }
473            if (touchedWin != null) {
474                if (false && DEBUG_DRAG) {
475                    Slog.d(TAG_WM, "sending DRAG_LOCATION to " + touchedWin);
476                }
477                DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DRAG_LOCATION,
478                        x, y, null, null, null, null, false);
479                touchedWin.mClient.dispatchDragEvent(evt);
480                if (myPid != touchedWin.mSession.mPid) {
481                    evt.recycle();
482                }
483            }
484        } catch (RemoteException e) {
485            Slog.w(TAG_WM, "can't send drag notification to windows");
486        }
487        mTargetWindow = touchedWin;
488    }
489
490    // Find the drop target and tell it about the data.  Returns 'true' if we can immediately
491    // dispatch the global drag-ended message, 'false' if we need to wait for a
492    // result from the recipient.
493    boolean notifyDropLw(float x, float y) {
494        if (mAnimation != null) {
495            return false;
496        }
497        mCurrentX = x;
498        mCurrentY = y;
499
500        WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y);
501
502        if (!isWindowNotified(touchedWin)) {
503            // "drop" outside a valid window -- no recipient to apply a
504            // timeout to, and we can send the drag-ended message immediately.
505            mDragResult = false;
506            return true;
507        }
508
509        if (DEBUG_DRAG) {
510            Slog.d(TAG_WM, "sending DROP to " + touchedWin);
511        }
512
513        final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid());
514
515        DragAndDropPermissionsHandler dragAndDropPermissions = null;
516        if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 &&
517                (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) {
518            dragAndDropPermissions = new DragAndDropPermissionsHandler(
519                    mData,
520                    mUid,
521                    touchedWin.getOwningPackage(),
522                    mFlags & DRAG_FLAGS_URI_PERMISSIONS,
523                    mSourceUserId,
524                    targetUserId);
525        }
526        if (mSourceUserId != targetUserId){
527            mData.fixUris(mSourceUserId);
528        }
529        final int myPid = Process.myPid();
530        final IBinder token = touchedWin.mClient.asBinder();
531        DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y,
532                null, null, mData, dragAndDropPermissions, false);
533        try {
534            touchedWin.mClient.dispatchDragEvent(evt);
535
536            // 5 second timeout for this window to respond to the drop
537            mService.mH.removeMessages(H.DRAG_END_TIMEOUT, token);
538            Message msg = mService.mH.obtainMessage(H.DRAG_END_TIMEOUT, token);
539            mService.mH.sendMessageDelayed(msg, 5000);
540        } catch (RemoteException e) {
541            Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin);
542            return true;
543        } finally {
544            if (myPid != touchedWin.mSession.mPid) {
545                evt.recycle();
546            }
547        }
548        mToken = token;
549        return false;
550    }
551
552    private static DragEvent obtainDragEvent(WindowState win, int action,
553            float x, float y, Object localState,
554            ClipDescription description, ClipData data,
555            IDragAndDropPermissions dragAndDropPermissions,
556            boolean result) {
557        final float winX = win.translateToWindowX(x);
558        final float winY = win.translateToWindowY(y);
559        return DragEvent.obtain(action, winX, winY, localState, description, data,
560                dragAndDropPermissions, result);
561    }
562
563    boolean stepAnimationLocked(long currentTimeMs) {
564        if (mAnimation == null) {
565            return false;
566        }
567
568        mTransformation.clear();
569        if (!mAnimation.getTransformation(currentTimeMs, mTransformation)) {
570            cleanUpDragLw();
571            return false;
572        }
573
574        mTransformation.getMatrix().postTranslate(
575                mCurrentX - mThumbOffsetX, mCurrentY - mThumbOffsetY);
576        final float tmpFloats[] = mService.mTmpFloats;
577        mTransformation.getMatrix().getValues(tmpFloats);
578        mSurfaceControl.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
579        mSurfaceControl.setAlpha(mTransformation.getAlpha());
580        mSurfaceControl.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
581                tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
582        return true;
583    }
584
585    private Animation createReturnAnimationLocked() {
586        final AnimationSet set = new AnimationSet(false);
587        final float translateX = mOriginalX - mCurrentX;
588        final float translateY = mOriginalY - mCurrentY;
589        set.addAnimation(new TranslateAnimation( 0, translateX, 0, translateY));
590        set.addAnimation(new AlphaAnimation(mOriginalAlpha, mOriginalAlpha / 2));
591        // Adjust the duration to the travel distance.
592        final double travelDistance = Math.sqrt(translateX * translateX + translateY * translateY);
593        final double displayDiagonal =
594                Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y);
595        final long duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal
596                * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS));
597        set.setDuration(duration);
598        set.setInterpolator(mCubicEaseOutInterpolator);
599        set.initialize(0, 0, 0, 0);
600        set.start();  // Will start on the first call to getTransformation.
601        return set;
602    }
603
604    private Animation createCancelAnimationLocked() {
605        final AnimationSet set = new AnimationSet(false);
606        set.addAnimation(new ScaleAnimation(1, 0, 1, 0, mThumbOffsetX, mThumbOffsetY));
607        set.addAnimation(new AlphaAnimation(mOriginalAlpha, 0));
608        set.setDuration(MIN_ANIMATION_DURATION_MS);
609        set.setInterpolator(mCubicEaseOutInterpolator);
610        set.initialize(0, 0, 0, 0);
611        set.start();  // Will start on the first call to getTransformation.
612        return set;
613    }
614
615    private boolean isFromSource(int source) {
616        return (mTouchSource & source) == source;
617    }
618
619    void overridePointerIconLw(int touchSource) {
620        mTouchSource = touchSource;
621        if (isFromSource(InputDevice.SOURCE_MOUSE)) {
622            InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING);
623        }
624    }
625}
626