Loading libs/WindowManager/Shell/res/values/dimen.xml +24 −0 Original line number Diff line number Diff line Loading @@ -297,4 +297,28 @@ when the pinned stack size is overridden by app via minWidth/minHeight. --> <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen> <!-- The size of the drag handle / menu shown along with a floating task. --> <dimen name="floating_task_menu_size">32dp</dimen> <!-- The size of menu items in the floating task menu. --> <dimen name="floating_task_menu_item_size">24dp</dimen> <!-- The horizontal margin of menu items in the floating task menu. --> <dimen name="floating_task_menu_item_padding">5dp</dimen> <!-- The width of visible floating view region when stashed. --> <dimen name="floating_task_stash_offset">32dp</dimen> <!-- The amount of elevation for a floating task. --> <dimen name="floating_task_elevation">8dp</dimen> <!-- The amount of padding around the bottom and top of the task. --> <dimen name="floating_task_vertical_padding">8dp</dimen> <!-- The normal size of the dismiss target. --> <dimen name="floating_task_dismiss_circle_size">150dp</dimen> <!-- The smaller size of the dismiss target (shrinks when something is in the target). --> <dimen name="floating_dismiss_circle_small">120dp</dimen> </resources> libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java +2 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,8 @@ public class DismissCircleView extends FrameLayout { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); final Resources res = getResources(); setBackground(res.getDrawable(R.drawable.dismiss_circle_background)); setViewSizes(); } Loading libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +42 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.content.pm.PackageManager; import android.os.Handler; import android.os.SystemProperties; import android.view.IWindowManager; import android.view.WindowManager; import com.android.internal.logging.UiEventLogger; import com.android.launcher3.icons.IconProvider; Loading Loading @@ -60,6 +61,8 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.floating.FloatingTasks; import com.android.wm.shell.floating.FloatingTasksController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; Loading Loading @@ -571,6 +574,45 @@ public abstract class WMShellBaseModule { return Optional.empty(); } // // Floating tasks // @WMSingleton @Provides static Optional<FloatingTasks> provideFloatingTasks( Optional<FloatingTasksController> floatingTaskController) { return floatingTaskController.map((controller) -> controller.asFloatingTasks()); } @WMSingleton @Provides static Optional<FloatingTasksController> provideFloatingTasksController(Context context, ShellInit shellInit, ShellController shellController, ShellCommandHandler shellCommandHandler, WindowManager windowManager, ShellTaskOrganizer organizer, TaskViewTransitions taskViewTransitions, @ShellMainThread ShellExecutor mainExecutor, @ShellBackgroundThread ShellExecutor bgExecutor, SyncTransactionQueue syncQueue) { if (FloatingTasksController.FLOATING_TASKS_ENABLED) { return Optional.of(new FloatingTasksController(context, shellInit, shellController, shellCommandHandler, windowManager, organizer, taskViewTransitions, mainExecutor, bgExecutor, syncQueue)); } else { return Optional.empty(); } } // // Starting window // Loading libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java 0 → 100644 +259 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.wm.shell.floating; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.view.MotionEvent; import android.view.View; import androidx.annotation.NonNull; import androidx.dynamicanimation.animation.DynamicAnimation; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.DismissView; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.android.wm.shell.floating.views.FloatingTaskLayer; import com.android.wm.shell.floating.views.FloatingTaskView; import java.util.Objects; /** * Controls a floating dismiss circle that has a 'magnetic' field around it, causing views moved * close to the target to be stuck to it unless moved out again. */ public class FloatingDismissController { /** Velocity required to dismiss the view without dragging it into the dismiss target. */ private static final float FLING_TO_DISMISS_MIN_VELOCITY = 4000f; /** * Max velocity that the view can be moving through the target with to stick (i.e. if it's * more than this velocity, it will pass through the target. */ private static final float STICK_TO_TARGET_MAX_X_VELOCITY = 2000f; /** * Percentage of the target width to use to determine if an object flung towards the target * should dismiss (e.g. if target is 100px and this is set ot 2f, anything flung within a * 200px-wide area around the target will be considered 'near' enough get dismissed). */ private static final float FLING_TO_TARGET_WIDTH_PERCENT = 2f; /** Minimum alpha to apply to the view being dismissed when it is in the target. */ private static final float DISMISS_VIEW_MIN_ALPHA = 0.6f; /** Amount to scale down the view being dismissed when it is in the target. */ private static final float DISMISS_VIEW_SCALE_DOWN_PERCENT = 0.15f; private Context mContext; private FloatingTasksController mController; private FloatingTaskLayer mParent; private DismissView mDismissView; private ValueAnimator mDismissAnimator; private View mViewBeingDismissed; private float mDismissSizePercent; private float mDismissSize; /** * The currently magnetized object, which is being dragged and will be attracted to the magnetic * dismiss target. */ private MagnetizedObject<View> mMagnetizedObject; /** * The MagneticTarget instance for our circular dismiss view. This is added to the * MagnetizedObject instances for the view being dragged. */ private MagnetizedObject.MagneticTarget mMagneticTarget; /** Magnet listener that handles animating and dismissing the view. */ private MagnetizedObject.MagnetListener mFloatingViewMagnetListener; public FloatingDismissController(Context context, FloatingTasksController controller, FloatingTaskLayer parent) { mContext = context; mController = controller; mParent = parent; updateSizes(); createAndAddDismissView(); mDismissAnimator = ValueAnimator.ofFloat(1f, 0f); mDismissAnimator.addUpdateListener(animation -> { final float value = (float) animation.getAnimatedValue(); if (mDismissView != null) { mDismissView.setPivotX((mDismissView.getRight() - mDismissView.getLeft()) / 2f); mDismissView.setPivotY((mDismissView.getBottom() - mDismissView.getTop()) / 2f); final float scaleValue = Math.max(value, mDismissSizePercent); mDismissView.getCircle().setScaleX(scaleValue); mDismissView.getCircle().setScaleY(scaleValue); } if (mViewBeingDismissed != null) { // TODO: alpha doesn't actually apply to taskView currently. mViewBeingDismissed.setAlpha(Math.max(value, DISMISS_VIEW_MIN_ALPHA)); mViewBeingDismissed.setScaleX(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT)); mViewBeingDismissed.setScaleY(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT)); } }); mFloatingViewMagnetListener = new MagnetizedObject.MagnetListener() { @Override public void onStuckToTarget( @NonNull MagnetizedObject.MagneticTarget target) { animateDismissing(/* dismissing= */ true); } @Override public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, float velX, float velY, boolean wasFlungOut) { animateDismissing(/* dismissing= */ false); mParent.onUnstuckFromTarget((FloatingTaskView) mViewBeingDismissed, velX, velY, wasFlungOut); } @Override public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { doDismiss(); } }; } /** Updates all the sizes used and applies them to the {@link DismissView}. */ public void updateSizes() { Resources res = mContext.getResources(); mDismissSize = res.getDimensionPixelSize( R.dimen.floating_task_dismiss_circle_size); final float minDismissSize = res.getDimensionPixelSize( R.dimen.floating_dismiss_circle_small); mDismissSizePercent = minDismissSize / mDismissSize; if (mDismissView != null) { mDismissView.updateResources(); } } /** Prepares the view being dragged to be magnetic. */ public void setUpMagneticObject(View viewBeingDragged) { mViewBeingDismissed = viewBeingDragged; mMagnetizedObject = getMagnetizedView(viewBeingDragged); mMagnetizedObject.clearAllTargets(); mMagnetizedObject.addTarget(mMagneticTarget); mMagnetizedObject.setMagnetListener(mFloatingViewMagnetListener); } /** Shows or hides the dismiss target. */ public void showDismiss(boolean show) { if (show) { mDismissView.show(); } else { mDismissView.hide(); } } /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */ public boolean passEventToMagnetizedObject(MotionEvent event) { return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event); } private void createAndAddDismissView() { if (mDismissView != null) { mParent.removeView(mDismissView); } mDismissView = new DismissView(mContext); mDismissView.setTargetSizeResId(R.dimen.floating_task_dismiss_circle_size); mDismissView.updateResources(); mParent.addView(mDismissView); final float dismissRadius = mDismissSize; // Save the MagneticTarget instance for the newly set up view - we'll add this to the // MagnetizedObjects when the dismiss view gets shown. mMagneticTarget = new MagnetizedObject.MagneticTarget( mDismissView.getCircle(), (int) dismissRadius); } private MagnetizedObject<View> getMagnetizedView(View v) { if (mMagnetizedObject != null && Objects.equals(mMagnetizedObject.getUnderlyingObject(), v)) { // Same view being dragged, we can reuse the magnetic object. return mMagnetizedObject; } MagnetizedObject<View> magnetizedView = new MagnetizedObject<View>( mContext, v, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y ) { @Override public float getWidth(@NonNull View underlyingObject) { return underlyingObject.getWidth(); } @Override public float getHeight(@NonNull View underlyingObject) { return underlyingObject.getHeight(); } @Override public void getLocationOnScreen(@NonNull View underlyingObject, @NonNull int[] loc) { loc[0] = (int) underlyingObject.getTranslationX(); loc[1] = (int) underlyingObject.getTranslationY(); } }; magnetizedView.setHapticsEnabled(true); magnetizedView.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); magnetizedView.setStickToTargetMaxXVelocity(STICK_TO_TARGET_MAX_X_VELOCITY); magnetizedView.setFlingToTargetWidthPercent(FLING_TO_TARGET_WIDTH_PERCENT); return magnetizedView; } /** Animates the dismiss treatment on the view being dismissed. */ private void animateDismissing(boolean shouldDismiss) { if (mViewBeingDismissed == null) { return; } if (shouldDismiss) { mDismissAnimator.removeAllListeners(); mDismissAnimator.start(); } else { mDismissAnimator.removeAllListeners(); mDismissAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); resetDismissAnimator(); } }); mDismissAnimator.reverse(); } } /** Actually dismisses the view. */ private void doDismiss() { mDismissView.hide(); mController.removeTask(); resetDismissAnimator(); mViewBeingDismissed = null; } private void resetDismissAnimator() { mDismissAnimator.removeAllListeners(); mDismissAnimator.cancel(); if (mDismissView != null) { mDismissView.cancelAnimators(); mDismissView.getCircle().setScaleX(1f); mDismissView.getCircle().setScaleY(1f); } } } libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java 0 → 100644 +41 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.wm.shell.floating; import android.content.Intent; import com.android.wm.shell.common.annotations.ExternalThread; /** * Interface to interact with floating tasks. */ @ExternalThread public interface FloatingTasks { /** * Shows, stashes, or un-stashes the floating task depending on state: * - If there is no floating task for this intent, it shows the task for the provided intent. * - If there is a floating task for this intent, but it's stashed, this un-stashes it. * - If there is a floating task for this intent, and it's not stashed, this stashes it. */ void showOrSetStashed(Intent intent); /** Returns a binder that can be passed to an external process to manipulate FloatingTasks. */ default IFloatingTasks createExternalInterface() { return null; } } Loading
libs/WindowManager/Shell/res/values/dimen.xml +24 −0 Original line number Diff line number Diff line Loading @@ -297,4 +297,28 @@ when the pinned stack size is overridden by app via minWidth/minHeight. --> <dimen name="overridable_minimal_size_pip_resizable_task">48dp</dimen> <!-- The size of the drag handle / menu shown along with a floating task. --> <dimen name="floating_task_menu_size">32dp</dimen> <!-- The size of menu items in the floating task menu. --> <dimen name="floating_task_menu_item_size">24dp</dimen> <!-- The horizontal margin of menu items in the floating task menu. --> <dimen name="floating_task_menu_item_padding">5dp</dimen> <!-- The width of visible floating view region when stashed. --> <dimen name="floating_task_stash_offset">32dp</dimen> <!-- The amount of elevation for a floating task. --> <dimen name="floating_task_elevation">8dp</dimen> <!-- The amount of padding around the bottom and top of the task. --> <dimen name="floating_task_vertical_padding">8dp</dimen> <!-- The normal size of the dismiss target. --> <dimen name="floating_task_dismiss_circle_size">150dp</dimen> <!-- The smaller size of the dismiss target (shrinks when something is in the target). --> <dimen name="floating_dismiss_circle_small">120dp</dimen> </resources>
libs/WindowManager/Shell/src/com/android/wm/shell/common/DismissCircleView.java +2 −0 Original line number Diff line number Diff line Loading @@ -49,6 +49,8 @@ public class DismissCircleView extends FrameLayout { @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); final Resources res = getResources(); setBackground(res.getDrawable(R.drawable.dismiss_circle_background)); setViewSizes(); } Loading
libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +42 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import android.content.pm.PackageManager; import android.os.Handler; import android.os.SystemProperties; import android.view.IWindowManager; import android.view.WindowManager; import com.android.internal.logging.UiEventLogger; import com.android.launcher3.icons.IconProvider; Loading Loading @@ -60,6 +61,8 @@ import com.android.wm.shell.desktopmode.DesktopModeTaskRepository; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; import com.android.wm.shell.displayareahelper.DisplayAreaHelperController; import com.android.wm.shell.draganddrop.DragAndDropController; import com.android.wm.shell.floating.FloatingTasks; import com.android.wm.shell.floating.FloatingTasksController; import com.android.wm.shell.freeform.FreeformComponents; import com.android.wm.shell.fullscreen.FullscreenTaskListener; import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController; Loading Loading @@ -571,6 +574,45 @@ public abstract class WMShellBaseModule { return Optional.empty(); } // // Floating tasks // @WMSingleton @Provides static Optional<FloatingTasks> provideFloatingTasks( Optional<FloatingTasksController> floatingTaskController) { return floatingTaskController.map((controller) -> controller.asFloatingTasks()); } @WMSingleton @Provides static Optional<FloatingTasksController> provideFloatingTasksController(Context context, ShellInit shellInit, ShellController shellController, ShellCommandHandler shellCommandHandler, WindowManager windowManager, ShellTaskOrganizer organizer, TaskViewTransitions taskViewTransitions, @ShellMainThread ShellExecutor mainExecutor, @ShellBackgroundThread ShellExecutor bgExecutor, SyncTransactionQueue syncQueue) { if (FloatingTasksController.FLOATING_TASKS_ENABLED) { return Optional.of(new FloatingTasksController(context, shellInit, shellController, shellCommandHandler, windowManager, organizer, taskViewTransitions, mainExecutor, bgExecutor, syncQueue)); } else { return Optional.empty(); } } // // Starting window // Loading
libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java 0 → 100644 +259 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.wm.shell.floating; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.view.MotionEvent; import android.view.View; import androidx.annotation.NonNull; import androidx.dynamicanimation.animation.DynamicAnimation; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.DismissView; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; import com.android.wm.shell.floating.views.FloatingTaskLayer; import com.android.wm.shell.floating.views.FloatingTaskView; import java.util.Objects; /** * Controls a floating dismiss circle that has a 'magnetic' field around it, causing views moved * close to the target to be stuck to it unless moved out again. */ public class FloatingDismissController { /** Velocity required to dismiss the view without dragging it into the dismiss target. */ private static final float FLING_TO_DISMISS_MIN_VELOCITY = 4000f; /** * Max velocity that the view can be moving through the target with to stick (i.e. if it's * more than this velocity, it will pass through the target. */ private static final float STICK_TO_TARGET_MAX_X_VELOCITY = 2000f; /** * Percentage of the target width to use to determine if an object flung towards the target * should dismiss (e.g. if target is 100px and this is set ot 2f, anything flung within a * 200px-wide area around the target will be considered 'near' enough get dismissed). */ private static final float FLING_TO_TARGET_WIDTH_PERCENT = 2f; /** Minimum alpha to apply to the view being dismissed when it is in the target. */ private static final float DISMISS_VIEW_MIN_ALPHA = 0.6f; /** Amount to scale down the view being dismissed when it is in the target. */ private static final float DISMISS_VIEW_SCALE_DOWN_PERCENT = 0.15f; private Context mContext; private FloatingTasksController mController; private FloatingTaskLayer mParent; private DismissView mDismissView; private ValueAnimator mDismissAnimator; private View mViewBeingDismissed; private float mDismissSizePercent; private float mDismissSize; /** * The currently magnetized object, which is being dragged and will be attracted to the magnetic * dismiss target. */ private MagnetizedObject<View> mMagnetizedObject; /** * The MagneticTarget instance for our circular dismiss view. This is added to the * MagnetizedObject instances for the view being dragged. */ private MagnetizedObject.MagneticTarget mMagneticTarget; /** Magnet listener that handles animating and dismissing the view. */ private MagnetizedObject.MagnetListener mFloatingViewMagnetListener; public FloatingDismissController(Context context, FloatingTasksController controller, FloatingTaskLayer parent) { mContext = context; mController = controller; mParent = parent; updateSizes(); createAndAddDismissView(); mDismissAnimator = ValueAnimator.ofFloat(1f, 0f); mDismissAnimator.addUpdateListener(animation -> { final float value = (float) animation.getAnimatedValue(); if (mDismissView != null) { mDismissView.setPivotX((mDismissView.getRight() - mDismissView.getLeft()) / 2f); mDismissView.setPivotY((mDismissView.getBottom() - mDismissView.getTop()) / 2f); final float scaleValue = Math.max(value, mDismissSizePercent); mDismissView.getCircle().setScaleX(scaleValue); mDismissView.getCircle().setScaleY(scaleValue); } if (mViewBeingDismissed != null) { // TODO: alpha doesn't actually apply to taskView currently. mViewBeingDismissed.setAlpha(Math.max(value, DISMISS_VIEW_MIN_ALPHA)); mViewBeingDismissed.setScaleX(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT)); mViewBeingDismissed.setScaleY(Math.max(value, DISMISS_VIEW_SCALE_DOWN_PERCENT)); } }); mFloatingViewMagnetListener = new MagnetizedObject.MagnetListener() { @Override public void onStuckToTarget( @NonNull MagnetizedObject.MagneticTarget target) { animateDismissing(/* dismissing= */ true); } @Override public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target, float velX, float velY, boolean wasFlungOut) { animateDismissing(/* dismissing= */ false); mParent.onUnstuckFromTarget((FloatingTaskView) mViewBeingDismissed, velX, velY, wasFlungOut); } @Override public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) { doDismiss(); } }; } /** Updates all the sizes used and applies them to the {@link DismissView}. */ public void updateSizes() { Resources res = mContext.getResources(); mDismissSize = res.getDimensionPixelSize( R.dimen.floating_task_dismiss_circle_size); final float minDismissSize = res.getDimensionPixelSize( R.dimen.floating_dismiss_circle_small); mDismissSizePercent = minDismissSize / mDismissSize; if (mDismissView != null) { mDismissView.updateResources(); } } /** Prepares the view being dragged to be magnetic. */ public void setUpMagneticObject(View viewBeingDragged) { mViewBeingDismissed = viewBeingDragged; mMagnetizedObject = getMagnetizedView(viewBeingDragged); mMagnetizedObject.clearAllTargets(); mMagnetizedObject.addTarget(mMagneticTarget); mMagnetizedObject.setMagnetListener(mFloatingViewMagnetListener); } /** Shows or hides the dismiss target. */ public void showDismiss(boolean show) { if (show) { mDismissView.show(); } else { mDismissView.hide(); } } /** Passes the MotionEvent to the magnetized object and returns true if it was consumed. */ public boolean passEventToMagnetizedObject(MotionEvent event) { return mMagnetizedObject != null && mMagnetizedObject.maybeConsumeMotionEvent(event); } private void createAndAddDismissView() { if (mDismissView != null) { mParent.removeView(mDismissView); } mDismissView = new DismissView(mContext); mDismissView.setTargetSizeResId(R.dimen.floating_task_dismiss_circle_size); mDismissView.updateResources(); mParent.addView(mDismissView); final float dismissRadius = mDismissSize; // Save the MagneticTarget instance for the newly set up view - we'll add this to the // MagnetizedObjects when the dismiss view gets shown. mMagneticTarget = new MagnetizedObject.MagneticTarget( mDismissView.getCircle(), (int) dismissRadius); } private MagnetizedObject<View> getMagnetizedView(View v) { if (mMagnetizedObject != null && Objects.equals(mMagnetizedObject.getUnderlyingObject(), v)) { // Same view being dragged, we can reuse the magnetic object. return mMagnetizedObject; } MagnetizedObject<View> magnetizedView = new MagnetizedObject<View>( mContext, v, DynamicAnimation.TRANSLATION_X, DynamicAnimation.TRANSLATION_Y ) { @Override public float getWidth(@NonNull View underlyingObject) { return underlyingObject.getWidth(); } @Override public float getHeight(@NonNull View underlyingObject) { return underlyingObject.getHeight(); } @Override public void getLocationOnScreen(@NonNull View underlyingObject, @NonNull int[] loc) { loc[0] = (int) underlyingObject.getTranslationX(); loc[1] = (int) underlyingObject.getTranslationY(); } }; magnetizedView.setHapticsEnabled(true); magnetizedView.setFlingToTargetMinVelocity(FLING_TO_DISMISS_MIN_VELOCITY); magnetizedView.setStickToTargetMaxXVelocity(STICK_TO_TARGET_MAX_X_VELOCITY); magnetizedView.setFlingToTargetWidthPercent(FLING_TO_TARGET_WIDTH_PERCENT); return magnetizedView; } /** Animates the dismiss treatment on the view being dismissed. */ private void animateDismissing(boolean shouldDismiss) { if (mViewBeingDismissed == null) { return; } if (shouldDismiss) { mDismissAnimator.removeAllListeners(); mDismissAnimator.start(); } else { mDismissAnimator.removeAllListeners(); mDismissAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); resetDismissAnimator(); } }); mDismissAnimator.reverse(); } } /** Actually dismisses the view. */ private void doDismiss() { mDismissView.hide(); mController.removeTask(); resetDismissAnimator(); mViewBeingDismissed = null; } private void resetDismissAnimator() { mDismissAnimator.removeAllListeners(); mDismissAnimator.cancel(); if (mDismissView != null) { mDismissView.cancelAnimators(); mDismissView.getCircle().setScaleX(1f); mDismissView.getCircle().setScaleY(1f); } } }
libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java 0 → 100644 +41 −0 Original line number Diff line number Diff line /* * Copyright (C) 2022 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.wm.shell.floating; import android.content.Intent; import com.android.wm.shell.common.annotations.ExternalThread; /** * Interface to interact with floating tasks. */ @ExternalThread public interface FloatingTasks { /** * Shows, stashes, or un-stashes the floating task depending on state: * - If there is no floating task for this intent, it shows the task for the provided intent. * - If there is a floating task for this intent, but it's stashed, this un-stashes it. * - If there is a floating task for this intent, and it's not stashed, this stashes it. */ void showOrSetStashed(Intent intent); /** Returns a binder that can be passed to an external process to manipulate FloatingTasks. */ default IFloatingTasks createExternalInterface() { return null; } }