| /* |
| * Copyright (C) 2011 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.wm; |
| |
| import static com.android.server.wm.DragDropController.MSG_ANIMATION_END; |
| import static com.android.server.wm.DragDropController.MSG_DRAG_END_TIMEOUT; |
| import static com.android.server.wm.DragDropController.MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT; |
| import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG; |
| import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ORIENTATION; |
| import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS; |
| import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS; |
| import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; |
| |
| import android.animation.Animator; |
| import android.animation.PropertyValuesHolder; |
| import android.animation.ValueAnimator; |
| import android.annotation.Nullable; |
| import android.content.ClipData; |
| import android.content.ClipDescription; |
| import android.content.Context; |
| import android.graphics.Point; |
| import android.hardware.input.InputManager; |
| import android.os.Build; |
| import android.os.IBinder; |
| import android.os.Process; |
| import android.os.RemoteException; |
| import android.os.ServiceManager; |
| import android.os.UserHandle; |
| import android.os.UserManager; |
| import android.os.IUserManager; |
| import android.util.Slog; |
| import android.view.Display; |
| import android.view.DragEvent; |
| import android.view.InputChannel; |
| import android.view.InputDevice; |
| import android.view.PointerIcon; |
| import android.view.SurfaceControl; |
| import android.view.View; |
| import android.view.WindowManager; |
| import android.view.animation.DecelerateInterpolator; |
| import android.view.animation.Interpolator; |
| |
| import com.android.internal.view.IDragAndDropPermissions; |
| import com.android.server.input.InputApplicationHandle; |
| import com.android.server.input.InputWindowHandle; |
| |
| import java.util.ArrayList; |
| |
| /** |
| * Drag/drop state |
| */ |
| class DragState { |
| private static final long MIN_ANIMATION_DURATION_MS = 195; |
| private static final long MAX_ANIMATION_DURATION_MS = 375; |
| |
| private static final int DRAG_FLAGS_URI_ACCESS = View.DRAG_FLAG_GLOBAL_URI_READ | |
| View.DRAG_FLAG_GLOBAL_URI_WRITE; |
| |
| private static final int DRAG_FLAGS_URI_PERMISSIONS = DRAG_FLAGS_URI_ACCESS | |
| View.DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION | |
| View.DRAG_FLAG_GLOBAL_PREFIX_URI_PERMISSION; |
| |
| // Property names for animations |
| private static final String ANIMATED_PROPERTY_X = "x"; |
| private static final String ANIMATED_PROPERTY_Y = "y"; |
| private static final String ANIMATED_PROPERTY_ALPHA = "alpha"; |
| private static final String ANIMATED_PROPERTY_SCALE = "scale"; |
| |
| final WindowManagerService mService; |
| final DragDropController mDragDropController; |
| IBinder mToken; |
| /** |
| * Do not use the variable from the out of animation thread while mAnimator is not null. |
| */ |
| SurfaceControl mSurfaceControl; |
| int mFlags; |
| IBinder mLocalWin; |
| int mPid; |
| int mUid; |
| int mSourceUserId; |
| boolean mCrossProfileCopyAllowed; |
| ClipData mData; |
| ClipDescription mDataDescription; |
| int mTouchSource; |
| boolean mDragResult; |
| float mOriginalAlpha; |
| float mOriginalX, mOriginalY; |
| float mCurrentX, mCurrentY; |
| float mThumbOffsetX, mThumbOffsetY; |
| InputInterceptor mInputInterceptor; |
| WindowState mTargetWindow; |
| ArrayList<WindowState> mNotifiedWindows; |
| boolean mDragInProgress; |
| DisplayContent mDisplayContent; |
| |
| @Nullable private ValueAnimator mAnimator; |
| private final Interpolator mCubicEaseOutInterpolator = new DecelerateInterpolator(1.5f); |
| private Point mDisplaySize = new Point(); |
| |
| DragState(WindowManagerService service, IBinder token, SurfaceControl surface, |
| int flags, IBinder localWin) { |
| mService = service; |
| mDragDropController = service.mDragDropController; |
| mToken = token; |
| mSurfaceControl = surface; |
| mFlags = flags; |
| mLocalWin = localWin; |
| mNotifiedWindows = new ArrayList<WindowState>(); |
| } |
| |
| void reset() { |
| if (mAnimator != null) { |
| Slog.wtf(TAG_WM, |
| "Unexpectedly destroying mSurfaceControl while animation is running"); |
| } |
| if (mSurfaceControl != null) { |
| mSurfaceControl.destroy(); |
| } |
| mSurfaceControl = null; |
| mFlags = 0; |
| mLocalWin = null; |
| mToken = null; |
| mData = null; |
| mThumbOffsetX = mThumbOffsetY = 0; |
| mNotifiedWindows = null; |
| } |
| |
| class InputInterceptor { |
| InputChannel mServerChannel, mClientChannel; |
| DragInputEventReceiver mInputEventReceiver; |
| InputApplicationHandle mDragApplicationHandle; |
| InputWindowHandle mDragWindowHandle; |
| |
| InputInterceptor(Display display) { |
| InputChannel[] channels = InputChannel.openInputChannelPair("drag"); |
| mServerChannel = channels[0]; |
| mClientChannel = channels[1]; |
| mService.mInputManager.registerInputChannel(mServerChannel, null); |
| mInputEventReceiver = new DragInputEventReceiver(mClientChannel, |
| mService.mH.getLooper(), mDragDropController, mService); |
| |
| mDragApplicationHandle = new InputApplicationHandle(null); |
| mDragApplicationHandle.name = "drag"; |
| mDragApplicationHandle.dispatchingTimeoutNanos = |
| WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; |
| |
| mDragWindowHandle = new InputWindowHandle(mDragApplicationHandle, null, null, |
| display.getDisplayId()); |
| mDragWindowHandle.name = "drag"; |
| mDragWindowHandle.inputChannel = mServerChannel; |
| mDragWindowHandle.layer = getDragLayerLocked(); |
| mDragWindowHandle.layoutParamsFlags = 0; |
| mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; |
| mDragWindowHandle.dispatchingTimeoutNanos = |
| WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; |
| mDragWindowHandle.visible = true; |
| mDragWindowHandle.canReceiveKeys = false; |
| mDragWindowHandle.hasFocus = true; |
| mDragWindowHandle.hasWallpaper = false; |
| mDragWindowHandle.paused = false; |
| mDragWindowHandle.ownerPid = Process.myPid(); |
| mDragWindowHandle.ownerUid = Process.myUid(); |
| mDragWindowHandle.inputFeatures = 0; |
| mDragWindowHandle.scaleFactor = 1.0f; |
| |
| // The drag window cannot receive new touches. |
| mDragWindowHandle.touchableRegion.setEmpty(); |
| |
| // The drag window covers the entire display |
| mDragWindowHandle.frameLeft = 0; |
| mDragWindowHandle.frameTop = 0; |
| mDragWindowHandle.frameRight = mDisplaySize.x; |
| mDragWindowHandle.frameBottom = mDisplaySize.y; |
| |
| // Pause rotations before a drag. |
| if (DEBUG_ORIENTATION) { |
| Slog.d(TAG_WM, "Pausing rotation during drag"); |
| } |
| mService.pauseRotationLocked(); |
| } |
| |
| void tearDown() { |
| mService.mInputManager.unregisterInputChannel(mServerChannel); |
| mInputEventReceiver.dispose(); |
| mInputEventReceiver = null; |
| mClientChannel.dispose(); |
| mServerChannel.dispose(); |
| mClientChannel = null; |
| mServerChannel = null; |
| |
| mDragWindowHandle = null; |
| mDragApplicationHandle = null; |
| |
| // Resume rotations after a drag. |
| if (DEBUG_ORIENTATION) { |
| Slog.d(TAG_WM, "Resuming rotation after drag"); |
| } |
| mService.resumeRotationLocked(); |
| } |
| } |
| |
| InputChannel getInputChannel() { |
| return mInputInterceptor == null ? null : mInputInterceptor.mServerChannel; |
| } |
| |
| InputWindowHandle getInputWindowHandle() { |
| return mInputInterceptor == null ? null : mInputInterceptor.mDragWindowHandle; |
| } |
| |
| /** |
| * @param display The Display that the window being dragged is on. |
| */ |
| void register(Display display) { |
| display.getRealSize(mDisplaySize); |
| if (DEBUG_DRAG) Slog.d(TAG_WM, "registering drag input channel"); |
| if (mInputInterceptor != null) { |
| Slog.e(TAG_WM, "Duplicate register of drag input channel"); |
| } else { |
| mInputInterceptor = new InputInterceptor(display); |
| mService.mInputMonitor.updateInputWindowsLw(true /*force*/); |
| } |
| } |
| |
| void unregister() { |
| if (DEBUG_DRAG) Slog.d(TAG_WM, "unregistering drag input channel"); |
| if (mInputInterceptor == null) { |
| Slog.e(TAG_WM, "Unregister of nonexistent drag input channel"); |
| } else { |
| // Input channel should be disposed on the thread where the input is being handled. |
| mDragDropController.sendHandlerMessage( |
| MSG_TEAR_DOWN_DRAG_AND_DROP_INPUT, mInputInterceptor); |
| mInputInterceptor = null; |
| mService.mInputMonitor.updateInputWindowsLw(true /*force*/); |
| } |
| } |
| |
| int getDragLayerLocked() { |
| return mService.mPolicy.getWindowLayerFromTypeLw(WindowManager.LayoutParams.TYPE_DRAG) |
| * WindowManagerService.TYPE_LAYER_MULTIPLIER |
| + WindowManagerService.TYPE_LAYER_OFFSET; |
| } |
| |
| /* call out to each visible window/session informing it about the drag |
| */ |
| void broadcastDragStartedLocked(final float touchX, final float touchY) { |
| mOriginalX = mCurrentX = touchX; |
| mOriginalY = mCurrentY = touchY; |
| |
| // Cache a base-class instance of the clip metadata so that parceling |
| // works correctly in calling out to the apps. |
| mDataDescription = (mData != null) ? mData.getDescription() : null; |
| mNotifiedWindows.clear(); |
| mDragInProgress = true; |
| |
| mSourceUserId = UserHandle.getUserId(mUid); |
| |
| final IUserManager userManager = |
| (IUserManager) ServiceManager.getService(Context.USER_SERVICE); |
| try { |
| mCrossProfileCopyAllowed = !userManager.getUserRestrictions(mSourceUserId).getBoolean( |
| UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); |
| } catch (RemoteException e) { |
| Slog.e(TAG_WM, "Remote Exception calling UserManager: " + e); |
| mCrossProfileCopyAllowed = false; |
| } |
| |
| if (DEBUG_DRAG) { |
| Slog.d(TAG_WM, "broadcasting DRAG_STARTED at (" + touchX + ", " + touchY + ")"); |
| } |
| |
| mDisplayContent.forAllWindows(w -> { |
| sendDragStartedLocked(w, touchX, touchY, mDataDescription); |
| }, false /* traverseTopToBottom */ ); |
| } |
| |
| /* helper - send a ACTION_DRAG_STARTED event, if the |
| * designated window is potentially a drop recipient. There are race situations |
| * around DRAG_ENDED broadcast, so we make sure that once we've declared that |
| * the drag has ended, we never send out another DRAG_STARTED for this drag action. |
| * |
| * This method clones the 'event' parameter if it's being delivered to the same |
| * process, so it's safe for the caller to call recycle() on the event afterwards. |
| */ |
| private void sendDragStartedLocked(WindowState newWin, float touchX, float touchY, |
| ClipDescription desc) { |
| if (mDragInProgress && isValidDropTarget(newWin)) { |
| DragEvent event = obtainDragEvent(newWin, DragEvent.ACTION_DRAG_STARTED, |
| touchX, touchY, null, desc, null, null, false); |
| try { |
| newWin.mClient.dispatchDragEvent(event); |
| // track each window that we've notified that the drag is starting |
| mNotifiedWindows.add(newWin); |
| } catch (RemoteException e) { |
| Slog.w(TAG_WM, "Unable to drag-start window " + newWin); |
| } finally { |
| // if the callee was local, the dispatch has already recycled the event |
| if (Process.myPid() != newWin.mSession.mPid) { |
| event.recycle(); |
| } |
| } |
| } |
| } |
| |
| private boolean isValidDropTarget(WindowState targetWin) { |
| if (targetWin == null) { |
| return false; |
| } |
| if (!targetWin.isPotentialDragTarget()) { |
| return false; |
| } |
| if ((mFlags & View.DRAG_FLAG_GLOBAL) == 0 || !targetWindowSupportsGlobalDrag(targetWin)) { |
| // Drag is limited to the current window. |
| if (mLocalWin != targetWin.mClient.asBinder()) { |
| return false; |
| } |
| } |
| |
| return mCrossProfileCopyAllowed || |
| mSourceUserId == UserHandle.getUserId(targetWin.getOwningUid()); |
| } |
| |
| private boolean targetWindowSupportsGlobalDrag(WindowState targetWin) { |
| // Global drags are limited to system windows, and windows for apps that are targeting N and |
| // above. |
| return targetWin.mAppToken == null |
| || targetWin.mAppToken.mTargetSdk >= Build.VERSION_CODES.N; |
| } |
| |
| /* helper - send a ACTION_DRAG_STARTED event only if the window has not |
| * previously been notified, i.e. it became visible after the drag operation |
| * was begun. This is a rare case. |
| */ |
| void sendDragStartedIfNeededLocked(WindowState newWin) { |
| if (mDragInProgress) { |
| // If we have sent the drag-started, we needn't do so again |
| if (isWindowNotified(newWin)) { |
| return; |
| } |
| if (DEBUG_DRAG) { |
| Slog.d(TAG_WM, "need to send DRAG_STARTED to new window " + newWin); |
| } |
| sendDragStartedLocked(newWin, mCurrentX, mCurrentY, mDataDescription); |
| } |
| } |
| |
| private boolean isWindowNotified(WindowState newWin) { |
| for (WindowState ws : mNotifiedWindows) { |
| if (ws == newWin) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void broadcastDragEndedLocked() { |
| final int myPid = Process.myPid(); |
| |
| if (DEBUG_DRAG) { |
| Slog.d(TAG_WM, "broadcasting DRAG_ENDED"); |
| } |
| for (WindowState ws : mNotifiedWindows) { |
| float x = 0; |
| float y = 0; |
| if (!mDragResult && (ws.mSession.mPid == mPid)) { |
| // Report unconsumed drop location back to the app that started the drag. |
| x = mCurrentX; |
| y = mCurrentY; |
| } |
| DragEvent evt = DragEvent.obtain(DragEvent.ACTION_DRAG_ENDED, |
| x, y, null, null, null, null, mDragResult); |
| try { |
| ws.mClient.dispatchDragEvent(evt); |
| } catch (RemoteException e) { |
| Slog.w(TAG_WM, "Unable to drag-end window " + ws); |
| } |
| // if the current window is in the same process, |
| // the dispatch has already recycled the event |
| if (myPid != ws.mSession.mPid) { |
| evt.recycle(); |
| } |
| } |
| mNotifiedWindows.clear(); |
| mDragInProgress = false; |
| } |
| |
| void endDragLocked() { |
| if (mAnimator != null) { |
| return; |
| } |
| if (!mDragResult) { |
| mAnimator = createReturnAnimationLocked(); |
| return; // Will call cleanUpDragLw when the animation is done. |
| } |
| cleanUpDragLocked(); |
| } |
| |
| void cancelDragLocked() { |
| if (mAnimator != null) { |
| return; |
| } |
| if (!mDragInProgress) { |
| // This can happen if an app invokes Session#cancelDragAndDrop before |
| // Session#performDrag. Reset the drag state: |
| // 1. without sending the end broadcast because the start broadcast has not been sent, |
| // and |
| // 2. without playing the cancel animation because H.DRAG_START_TIMEOUT may be sent to |
| // WindowManagerService, which will cause DragState#reset() while playing the |
| // cancel animation. |
| reset(); |
| mDragDropController.mDragState = null; |
| return; |
| } |
| mAnimator = createCancelAnimationLocked(); |
| } |
| |
| private void cleanUpDragLocked() { |
| broadcastDragEndedLocked(); |
| if (isFromSource(InputDevice.SOURCE_MOUSE)) { |
| mService.restorePointerIconLocked(mDisplayContent, mCurrentX, mCurrentY); |
| } |
| |
| // stop intercepting input |
| unregister(); |
| |
| // free our resources and drop all the object references |
| reset(); |
| mDragDropController.mDragState = null; |
| } |
| |
| void notifyMoveLocked(float x, float y) { |
| if (mAnimator != null) { |
| return; |
| } |
| mCurrentX = x; |
| mCurrentY = y; |
| |
| // Move the surface to the given touch |
| if (SHOW_LIGHT_TRANSACTIONS) Slog.i( |
| TAG_WM, ">>> OPEN TRANSACTION notifyMoveLocked"); |
| mService.openSurfaceTransaction(); |
| try { |
| mSurfaceControl.setPosition(x - mThumbOffsetX, y - mThumbOffsetY); |
| if (SHOW_TRANSACTIONS) Slog.i(TAG_WM, " DRAG " |
| + mSurfaceControl + ": pos=(" + |
| (int)(x - mThumbOffsetX) + "," + (int)(y - mThumbOffsetY) + ")"); |
| } finally { |
| mService.closeSurfaceTransaction(); |
| if (SHOW_LIGHT_TRANSACTIONS) Slog.i( |
| TAG_WM, "<<< CLOSE TRANSACTION notifyMoveLocked"); |
| } |
| notifyLocationLocked(x, y); |
| } |
| |
| void notifyLocationLocked(float x, float y) { |
| // Tell the affected window |
| WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y); |
| if (touchedWin != null && !isWindowNotified(touchedWin)) { |
| // The drag point is over a window which was not notified about a drag start. |
| // Pretend it's over empty space. |
| touchedWin = null; |
| } |
| |
| try { |
| final int myPid = Process.myPid(); |
| |
| // have we dragged over a new window? |
| if ((touchedWin != mTargetWindow) && (mTargetWindow != null)) { |
| if (DEBUG_DRAG) { |
| Slog.d(TAG_WM, "sending DRAG_EXITED to " + mTargetWindow); |
| } |
| // force DRAG_EXITED_EVENT if appropriate |
| DragEvent evt = obtainDragEvent(mTargetWindow, DragEvent.ACTION_DRAG_EXITED, |
| 0, 0, null, null, null, null, false); |
| mTargetWindow.mClient.dispatchDragEvent(evt); |
| if (myPid != mTargetWindow.mSession.mPid) { |
| evt.recycle(); |
| } |
| } |
| if (touchedWin != null) { |
| if (false && DEBUG_DRAG) { |
| Slog.d(TAG_WM, "sending DRAG_LOCATION to " + touchedWin); |
| } |
| DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DRAG_LOCATION, |
| x, y, null, null, null, null, false); |
| touchedWin.mClient.dispatchDragEvent(evt); |
| if (myPid != touchedWin.mSession.mPid) { |
| evt.recycle(); |
| } |
| } |
| } catch (RemoteException e) { |
| Slog.w(TAG_WM, "can't send drag notification to windows"); |
| } |
| mTargetWindow = touchedWin; |
| } |
| |
| // Find the drop target and tell it about the data. Returns 'true' if we can immediately |
| // dispatch the global drag-ended message, 'false' if we need to wait for a |
| // result from the recipient. |
| boolean notifyDropLocked(float x, float y) { |
| if (mAnimator != null) { |
| return false; |
| } |
| mCurrentX = x; |
| mCurrentY = y; |
| |
| WindowState touchedWin = mDisplayContent.getTouchableWinAtPointLocked(x, y); |
| |
| if (!isWindowNotified(touchedWin)) { |
| // "drop" outside a valid window -- no recipient to apply a |
| // timeout to, and we can send the drag-ended message immediately. |
| mDragResult = false; |
| return true; |
| } |
| |
| if (DEBUG_DRAG) { |
| Slog.d(TAG_WM, "sending DROP to " + touchedWin); |
| } |
| |
| final int targetUserId = UserHandle.getUserId(touchedWin.getOwningUid()); |
| |
| DragAndDropPermissionsHandler dragAndDropPermissions = null; |
| if ((mFlags & View.DRAG_FLAG_GLOBAL) != 0 && |
| (mFlags & DRAG_FLAGS_URI_ACCESS) != 0) { |
| dragAndDropPermissions = new DragAndDropPermissionsHandler( |
| mData, |
| mUid, |
| touchedWin.getOwningPackage(), |
| mFlags & DRAG_FLAGS_URI_PERMISSIONS, |
| mSourceUserId, |
| targetUserId); |
| } |
| if (mSourceUserId != targetUserId){ |
| mData.fixUris(mSourceUserId); |
| } |
| final int myPid = Process.myPid(); |
| final IBinder token = touchedWin.mClient.asBinder(); |
| DragEvent evt = obtainDragEvent(touchedWin, DragEvent.ACTION_DROP, x, y, |
| null, null, mData, dragAndDropPermissions, false); |
| try { |
| touchedWin.mClient.dispatchDragEvent(evt); |
| |
| // 5 second timeout for this window to respond to the drop |
| mDragDropController.sendTimeoutMessage(MSG_DRAG_END_TIMEOUT, token); |
| } catch (RemoteException e) { |
| Slog.w(TAG_WM, "can't send drop notification to win " + touchedWin); |
| return true; |
| } finally { |
| if (myPid != touchedWin.mSession.mPid) { |
| evt.recycle(); |
| } |
| } |
| mToken = token; |
| return false; |
| } |
| |
| void onAnimationEndLocked() { |
| if (mAnimator == null) { |
| Slog.wtf(TAG_WM, "Unexpected null mAnimator"); |
| return; |
| } |
| mAnimator = null; |
| cleanUpDragLocked(); |
| } |
| |
| private static DragEvent obtainDragEvent(WindowState win, int action, |
| float x, float y, Object localState, |
| ClipDescription description, ClipData data, |
| IDragAndDropPermissions dragAndDropPermissions, |
| boolean result) { |
| final float winX = win.translateToWindowX(x); |
| final float winY = win.translateToWindowY(y); |
| return DragEvent.obtain(action, winX, winY, localState, description, data, |
| dragAndDropPermissions, result); |
| } |
| |
| private ValueAnimator createReturnAnimationLocked() { |
| final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder( |
| PropertyValuesHolder.ofFloat( |
| ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, |
| mOriginalX - mThumbOffsetX), |
| PropertyValuesHolder.ofFloat( |
| ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, |
| mOriginalY - mThumbOffsetY), |
| PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 1), |
| PropertyValuesHolder.ofFloat( |
| ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, mOriginalAlpha / 2)); |
| |
| final float translateX = mOriginalX - mCurrentX; |
| final float translateY = mOriginalY - mCurrentY; |
| // Adjust the duration to the travel distance. |
| final double travelDistance = Math.sqrt(translateX * translateX + translateY * translateY); |
| final double displayDiagonal = |
| Math.sqrt(mDisplaySize.x * mDisplaySize.x + mDisplaySize.y * mDisplaySize.y); |
| final long duration = MIN_ANIMATION_DURATION_MS + (long) (travelDistance / displayDiagonal |
| * (MAX_ANIMATION_DURATION_MS - MIN_ANIMATION_DURATION_MS)); |
| final AnimationListener listener = new AnimationListener(); |
| animator.setDuration(duration); |
| animator.setInterpolator(mCubicEaseOutInterpolator); |
| animator.addListener(listener); |
| animator.addUpdateListener(listener); |
| |
| mService.mAnimationHandler.post(() -> animator.start()); |
| return animator; |
| } |
| |
| private ValueAnimator createCancelAnimationLocked() { |
| final ValueAnimator animator = ValueAnimator.ofPropertyValuesHolder( |
| PropertyValuesHolder.ofFloat( |
| ANIMATED_PROPERTY_X, mCurrentX - mThumbOffsetX, mCurrentX), |
| PropertyValuesHolder.ofFloat( |
| ANIMATED_PROPERTY_Y, mCurrentY - mThumbOffsetY, mCurrentY), |
| PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_SCALE, 1, 0), |
| PropertyValuesHolder.ofFloat(ANIMATED_PROPERTY_ALPHA, mOriginalAlpha, 0)); |
| final AnimationListener listener = new AnimationListener(); |
| animator.setDuration(MIN_ANIMATION_DURATION_MS); |
| animator.setInterpolator(mCubicEaseOutInterpolator); |
| animator.addListener(listener); |
| animator.addUpdateListener(listener); |
| |
| mService.mAnimationHandler.post(() -> animator.start()); |
| return animator; |
| } |
| |
| private boolean isFromSource(int source) { |
| return (mTouchSource & source) == source; |
| } |
| |
| void overridePointerIconLocked(int touchSource) { |
| mTouchSource = touchSource; |
| if (isFromSource(InputDevice.SOURCE_MOUSE)) { |
| InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_GRABBING); |
| } |
| } |
| |
| private class AnimationListener |
| implements ValueAnimator.AnimatorUpdateListener, Animator.AnimatorListener { |
| @Override |
| public void onAnimationUpdate(ValueAnimator animation) { |
| try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) { |
| transaction.setPosition( |
| mSurfaceControl, |
| (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_X), |
| (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_Y)); |
| transaction.setAlpha( |
| mSurfaceControl, |
| (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_ALPHA)); |
| transaction.setMatrix( |
| mSurfaceControl, |
| (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0, |
| 0, (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE)); |
| transaction.apply(); |
| } |
| } |
| |
| @Override |
| public void onAnimationStart(Animator animator) {} |
| |
| @Override |
| public void onAnimationCancel(Animator animator) {} |
| |
| @Override |
| public void onAnimationRepeat(Animator animator) {} |
| |
| @Override |
| public void onAnimationEnd(Animator animator) { |
| // Updating mDragState requires the WM lock so continues it on the out of |
| // AnimationThread. |
| mDragDropController.sendHandlerMessage(MSG_ANIMATION_END, null); |
| } |
| } |
| } |