Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 46faccde authored by Mady Mellor's avatar Mady Mellor
Browse files

Remove FloatingTaskController & replace usages with Bubbles

Switch to using newly exposed method Bubbles#showAppBubble as what
FloatingTaskController does is no longer POR.

Calling this allows 1 bubble from a specific app to be added to the
stack. The only user of this method is NoteTaskController which is guarded by the NOTE_TASK.

Bug: 237678727
Test: treehugger
Change-Id: I47c5c010a050796eff848ba3fcabc40cf5fbd902
parent 3c57cb91
Loading
Loading
Loading
Loading
+29 −18
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;

import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
@@ -37,7 +38,6 @@ import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_LONGER_BUBBLE;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
import static com.android.wm.shell.floating.FloatingTasksController.SHOW_FLOATING_TASKS_AS_BUBBLES;

import android.annotation.NonNull;
import android.annotation.UserIdInt;
@@ -591,11 +591,6 @@ public class BubbleController implements ConfigurationChangeListener {
            }
            mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation);
        }
        if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubblePositioner.isLargeScreen()) {
            mBubblePositioner.setUsePinnedLocation(true);
        } else {
            mBubblePositioner.setUsePinnedLocation(false);
        }

        addToWindowManagerMaybe();
    }
@@ -959,14 +954,18 @@ public class BubbleController implements ConfigurationChangeListener {
    }

    /**
     * Adds a bubble for a specific intent. These bubbles are <b>not</b> backed by a notification
     * and remain until the user dismisses the bubble or bubble stack. Only one intent bubble
     * is supported at a time.
     * Adds and expands bubble for a specific intent. These bubbles are <b>not</b> backed by a n
     * otification and remain until the user dismisses the bubble or bubble stack. Only one intent
     * bubble is supported at a time.
     *
     * @param intent the intent to display in the bubble expanded view.
     */
    public void addAppBubble(Intent intent) {
    public void showAppBubble(Intent intent) {
        if (intent == null || intent.getPackage() == null) return;

        PackageManager packageManager = getPackageManagerForUser(mContext, mCurrentUserId);
        if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return;

        Bubble b = new Bubble(intent, UserHandle.of(mCurrentUserId), mMainExecutor);
        b.setShouldAutoExpand(true);
        inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
@@ -1489,18 +1488,23 @@ public class BubbleController implements ConfigurationChangeListener {
        }
        PackageManager packageManager = getPackageManagerForUser(
                context, entry.getStatusBarNotification().getUser().getIdentifier());
        ActivityInfo info =
                intent.getIntent().resolveActivityInfo(packageManager, 0);
        return isResizableActivity(intent.getIntent(), packageManager, entry.getKey());
    }

    static boolean isResizableActivity(Intent intent, PackageManager packageManager, String key) {
        if (intent == null) {
            Log.w(TAG, "Unable to send as bubble: " + key + " null intent");
            return false;
        }
        ActivityInfo info = intent.resolveActivityInfo(packageManager, 0);
        if (info == null) {
            Log.w(TAG, "Unable to send as bubble, "
                    + entry.getKey() + " couldn't find activity info for intent: "
                    + intent);
            Log.w(TAG, "Unable to send as bubble: " + key
                    + " couldn't find activity info for intent: " + intent);
            return false;
        }
        if (!ActivityInfo.isResizeableMode(info.resizeMode)) {
            Log.w(TAG, "Unable to send as bubble, "
                    + entry.getKey() + " activity is not resizable for intent: "
                    + intent);
            Log.w(TAG, "Unable to send as bubble: " + key
                    + " activity is not resizable for intent: " + intent);
            return false;
        }
        return true;
@@ -1673,6 +1677,13 @@ public class BubbleController implements ConfigurationChangeListener {
            });
        }

        @Override
        public void showAppBubble(Intent intent) {
            mMainExecutor.execute(() -> {
                BubbleController.this.showAppBubble(intent);
            });
        }

        @Override
        public boolean handleDismissalInterception(BubbleEntry entry,
                @Nullable List<BubbleEntry> children, IntConsumer removeCallback,
+10 −0
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.SOURCE;

import android.app.NotificationChannel;
import android.content.Intent;
import android.content.pm.UserInfo;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService;
@@ -107,6 +108,15 @@ public interface Bubbles {
     */
    void expandStackAndSelectBubble(Bubble bubble);

    /**
     * Adds and expands bubble that is not notification based, but instead based on an intent from
     * the app. The intent must be explicit (i.e. include a package name or fully qualified
     * component class name) and the activity for it should be resizable.
     *
     * @param intent the intent to populate the bubble.
     */
    void showAppBubble(Intent intent);

    /**
     * @return a bubble that matches the provided shortcutId, if one exists.
     */
+0 −44
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ 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;
@@ -65,8 +64,6 @@ 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;
@@ -574,47 +571,6 @@ 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,
            Optional<BubbleController> bubbleController,
            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,
                    bubbleController,
                    windowManager,
                    organizer,
                    taskViewTransitions,
                    mainExecutor,
                    bgExecutor,
                    syncQueue));
        } else {
            return Optional.empty();
        }
    }

    //
    // Starting window
    //
+0 −259
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);
        }
    }
}
+0 −36
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);
}
Loading