diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 725b20525bf7e..bec6844ae8631 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -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; @@ -150,6 +150,9 @@ public class BubbleController implements ConfigurationChangeListener { private final ShellExecutor mBackgroundExecutor; + // Whether or not we should show bubbles pinned at the bottom of the screen. + private boolean mIsBubbleBarEnabled; + private BubbleLogger mLogger; private BubbleData mBubbleData; @Nullable private BubbleStackView mStackView; @@ -210,7 +213,6 @@ public class BubbleController implements ConfigurationChangeListener { /** Drag and drop controller to register listener for onDragStarted. */ private DragAndDropController mDragAndDropController; - public BubbleController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, @@ -526,6 +528,12 @@ public class BubbleController implements ConfigurationChangeListener { mDataRepository.removeBubblesForUser(removedUserId, parentUserId); } + // TODO(b/256873975): Should pass this into the constructor once flags are available to shell. + /** Sets whether the bubble bar is enabled (i.e. bubbles pinned to bottom on large screens). */ + public void setBubbleBarEnabled(boolean enabled) { + mIsBubbleBarEnabled = enabled; + } + /** Whether this userId belongs to the current user. */ private boolean isCurrentProfile(int userId) { return userId == UserHandle.USER_ALL @@ -591,7 +599,8 @@ public class BubbleController implements ConfigurationChangeListener { } mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation); } - if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubblePositioner.isLargeScreen()) { + + if (mIsBubbleBarEnabled && mBubblePositioner.isLargeScreen()) { mBubblePositioner.setUsePinnedLocation(true); } else { mBubblePositioner.setUsePinnedLocation(false); @@ -959,14 +968,18 @@ public class BubbleController implements ConfigurationChangeListener { } /** - * Adds a bubble for a specific intent. These bubbles are not 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 not 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 +1502,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 +1691,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 children, IntConsumer removeCallback, @@ -1783,6 +1808,13 @@ public class BubbleController implements ConfigurationChangeListener { }); } + @Override + public void setBubbleBarEnabled(boolean enabled) { + mMainExecutor.execute(() -> { + BubbleController.this.setBubbleBarEnabled(enabled); + }); + } + @Override public void onNotificationPanelExpandedChanged(boolean expanded) { mMainExecutor.execute( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 7f891ec6d2150..465d1abe0a3df 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -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. */ @@ -232,6 +242,11 @@ public interface Bubbles { */ void onUserRemoved(int removedUserId); + /** + * Sets whether bubble bar should be enabled or not. + */ + void setBubbleBarEnabled(boolean enabled); + /** Listener to find out about stack expansion / collapse events. */ interface BubbleExpandListener { /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 625d8a83736d1..962be9da2111b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -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 provideFloatingTasks( - Optional floatingTaskController) { - return floatingTaskController.map((controller) -> controller.asFloatingTasks()); - } - - @WMSingleton - @Provides - static Optional provideFloatingTasksController(Context context, - ShellInit shellInit, - ShellController shellController, - ShellCommandHandler shellCommandHandler, - Optional 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 // diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java deleted file mode 100644 index 83a1734dc71ab..0000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * 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 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 getMagnetizedView(View v) { - if (mMagnetizedObject != null - && Objects.equals(mMagnetizedObject.getUnderlyingObject(), v)) { - // Same view being dragged, we can reuse the magnetic object. - return mMagnetizedObject; - } - MagnetizedObject magnetizedView = new MagnetizedObject( - 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); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java deleted file mode 100644 index f86d467360f9b..0000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * 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); -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java deleted file mode 100644 index b3c09d32055bd..0000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java +++ /dev/null @@ -1,454 +0,0 @@ -/* - * 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 static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - -import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission; -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS; -import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS; - -import android.annotation.Nullable; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ShortcutInfo; -import android.content.res.Configuration; -import android.graphics.PixelFormat; -import android.graphics.Point; -import android.os.SystemProperties; -import android.view.ViewGroup; -import android.view.WindowManager; - -import androidx.annotation.BinderThread; -import androidx.annotation.VisibleForTesting; - -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.TaskViewTransitions; -import com.android.wm.shell.bubbles.BubbleController; -import com.android.wm.shell.common.ExternalInterfaceBinder; -import com.android.wm.shell.common.RemoteCallable; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.common.annotations.ExternalThread; -import com.android.wm.shell.common.annotations.ShellBackgroundThread; -import com.android.wm.shell.common.annotations.ShellMainThread; -import com.android.wm.shell.floating.views.FloatingTaskLayer; -import com.android.wm.shell.floating.views.FloatingTaskView; -import com.android.wm.shell.sysui.ConfigurationChangeListener; -import com.android.wm.shell.sysui.ShellCommandHandler; -import com.android.wm.shell.sysui.ShellController; -import com.android.wm.shell.sysui.ShellInit; - -import java.io.PrintWriter; -import java.util.Objects; -import java.util.Optional; - -/** - * Entry point for creating and managing floating tasks. - * - * A single window layer is added and the task(s) are displayed using a {@link FloatingTaskView} - * within that window. - * - * Currently optimized for a single task. Multiple tasks are not supported. - */ -public class FloatingTasksController implements RemoteCallable, - ConfigurationChangeListener { - - private static final String TAG = FloatingTasksController.class.getSimpleName(); - - public static final boolean FLOATING_TASKS_ENABLED = - SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false); - public static final boolean SHOW_FLOATING_TASKS_AS_BUBBLES = - SystemProperties.getBoolean("persist.wm.debug.floating_tasks_as_bubbles", false); - - @VisibleForTesting - static final int SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET = 600; - - // Only used for testing - private Configuration mConfig; - private boolean mFloatingTasksEnabledForTests; - - private FloatingTaskImpl mImpl = new FloatingTaskImpl(); - private Context mContext; - private ShellController mShellController; - private ShellCommandHandler mShellCommandHandler; - private @Nullable BubbleController mBubbleController; - private WindowManager mWindowManager; - private ShellTaskOrganizer mTaskOrganizer; - private TaskViewTransitions mTaskViewTransitions; - private @ShellMainThread ShellExecutor mMainExecutor; - // TODO: mBackgroundThread is not used but we'll probs need it eventually? - private @ShellBackgroundThread ShellExecutor mBackgroundThread; - private SyncTransactionQueue mSyncQueue; - - private boolean mIsFloatingLayerAdded; - private FloatingTaskLayer mFloatingTaskLayer; - private final Point mLastPosition = new Point(-1, -1); - - private Task mTask; - - // Simple class to hold onto info for intent or shortcut based tasks. - public static class Task { - public int taskId = INVALID_TASK_ID; - @Nullable - public Intent intent; - @Nullable - public ShortcutInfo info; - @Nullable - public FloatingTaskView floatingView; - } - - public FloatingTasksController(Context context, - ShellInit shellInit, - ShellController shellController, - ShellCommandHandler shellCommandHandler, - Optional bubbleController, - WindowManager windowManager, - ShellTaskOrganizer organizer, - TaskViewTransitions transitions, - @ShellMainThread ShellExecutor mainExecutor, - @ShellBackgroundThread ShellExecutor bgExceutor, - SyncTransactionQueue syncTransactionQueue) { - mContext = context; - mShellController = shellController; - mShellCommandHandler = shellCommandHandler; - mBubbleController = bubbleController.get(); - mWindowManager = windowManager; - mTaskOrganizer = organizer; - mTaskViewTransitions = transitions; - mMainExecutor = mainExecutor; - mBackgroundThread = bgExceutor; - mSyncQueue = syncTransactionQueue; - if (isFloatingTasksEnabled()) { - shellInit.addInitCallback(this::onInit, this); - } - } - - protected void onInit() { - mShellController.addConfigurationChangeListener(this); - mShellController.addExternalInterface(KEY_EXTRA_SHELL_FLOATING_TASKS, - this::createExternalInterface, this); - mShellCommandHandler.addDumpCallback(this::dump, this); - } - - /** Only used for testing. */ - @VisibleForTesting - void setConfig(Configuration config) { - mConfig = config; - } - - /** Only used for testing. */ - @VisibleForTesting - void setFloatingTasksEnabled(boolean enabled) { - mFloatingTasksEnabledForTests = enabled; - } - - /** Whether the floating layer is available. */ - boolean isFloatingLayerAvailable() { - Configuration config = mConfig == null - ? mContext.getResources().getConfiguration() - : mConfig; - return config.smallestScreenWidthDp >= SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET; - } - - /** Whether floating tasks are enabled. */ - boolean isFloatingTasksEnabled() { - return FLOATING_TASKS_ENABLED || mFloatingTasksEnabledForTests; - } - - private ExternalInterfaceBinder createExternalInterface() { - return new IFloatingTasksImpl(this); - } - - @Override - public void onThemeChanged() { - if (mIsFloatingLayerAdded) { - mFloatingTaskLayer.updateSizes(); - } - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - // TODO: probably other stuff here to do (e.g. handle rotation) - if (mIsFloatingLayerAdded) { - mFloatingTaskLayer.updateSizes(); - } - } - - /** Returns false if the task shouldn't be shown. */ - private boolean canShowTask(Intent intent) { - ProtoLog.d(WM_SHELL_FLOATING_APPS, "canShowTask -- %s", intent); - if (!isFloatingTasksEnabled() || !isFloatingLayerAvailable()) return false; - if (intent == null) { - ProtoLog.e(WM_SHELL_FLOATING_APPS, "canShowTask given null intent, doing nothing"); - return false; - } - return true; - } - - /** Returns true if the task was or should be shown as a bubble. */ - private boolean maybeShowTaskAsBubble(Intent intent) { - if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubbleController != null) { - removeFloatingLayer(); - if (intent.getPackage() != null) { - mBubbleController.addAppBubble(intent); - ProtoLog.d(WM_SHELL_FLOATING_APPS, "showing floating task as bubble: %s", intent); - } else { - ProtoLog.d(WM_SHELL_FLOATING_APPS, - "failed to show floating task as bubble: %s; unknown package", intent); - } - return true; - } - return false; - } - - /** - * Shows, stashes, or un-stashes the floating task depending on state: - * - If there is no floating task for this intent, it shows this the provided task. - * - 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. - */ - public void showOrSetStashed(Intent intent) { - if (!canShowTask(intent)) return; - if (maybeShowTaskAsBubble(intent)) return; - - addFloatingLayer(); - - if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) { - // The task is already added, toggle the stash state. - mFloatingTaskLayer.setStashed(mTask, !mTask.floatingView.isStashed()); - return; - } - - // If we're here it's either a new or different task - showNewTask(intent); - } - - /** - * Shows a floating task with the provided intent. - * If the same task is present it will un-stash it or do nothing if it is already un-stashed. - * Removes any other floating tasks that might exist. - */ - public void showTask(Intent intent) { - if (!canShowTask(intent)) return; - if (maybeShowTaskAsBubble(intent)) return; - - addFloatingLayer(); - - if (isTaskAttached(mTask) && intent.filterEquals(mTask.intent)) { - // The task is already added, show it if it's stashed. - if (mTask.floatingView.isStashed()) { - mFloatingTaskLayer.setStashed(mTask, false); - } - return; - } - showNewTask(intent); - } - - private void showNewTask(Intent intent) { - if (mTask != null && !intent.filterEquals(mTask.intent)) { - mFloatingTaskLayer.removeAllTaskViews(); - mTask.floatingView.cleanUpTaskView(); - mTask = null; - } - - FloatingTaskView ftv = new FloatingTaskView(mContext, this); - ftv.createTaskView(mContext, mTaskOrganizer, mTaskViewTransitions, mSyncQueue); - - mTask = new Task(); - mTask.floatingView = ftv; - mTask.intent = intent; - - // Add & start the task. - mFloatingTaskLayer.addTask(mTask); - ProtoLog.d(WM_SHELL_FLOATING_APPS, "showNewTask, startingIntent: %s", intent); - mTask.floatingView.startTask(mMainExecutor, mTask); - } - - /** - * Removes the task and cleans up the view. - */ - public void removeTask() { - if (mTask != null) { - ProtoLog.d(WM_SHELL_FLOATING_APPS, "Removing task with id=%d", mTask.taskId); - - if (mTask.floatingView != null) { - // TODO: animate it - mFloatingTaskLayer.removeView(mTask.floatingView); - mTask.floatingView.cleanUpTaskView(); - } - removeFloatingLayer(); - } - } - - /** - * Whether there is a floating task and if it is stashed. - */ - public boolean isStashed() { - return isTaskAttached(mTask) && mTask.floatingView.isStashed(); - } - - /** - * If a floating task exists, this sets whether it is stashed and animates if needed. - */ - public void setStashed(boolean shouldStash) { - if (mTask != null && mTask.floatingView != null && mIsFloatingLayerAdded) { - mFloatingTaskLayer.setStashed(mTask, shouldStash); - } - } - - /** - * Saves the last position the floating task was in so that it can be put there again. - */ - public void setLastPosition(int x, int y) { - mLastPosition.set(x, y); - } - - /** - * Returns the last position the floating task was in. - */ - public Point getLastPosition() { - return mLastPosition; - } - - /** - * Whether the provided task has a view that's attached to the floating layer. - */ - private boolean isTaskAttached(Task t) { - return t != null && t.floatingView != null - && mIsFloatingLayerAdded - && mFloatingTaskLayer.getTaskViewCount() > 0 - && Objects.equals(mFloatingTaskLayer.getFirstTaskView(), t.floatingView); - } - - // TODO: when this is added, if there are bubbles, they get hidden? Is only one layer of this - // type allowed? Bubbles & floating tasks should probably be in the same layer to reduce - // # of windows. - private void addFloatingLayer() { - if (mIsFloatingLayerAdded) { - return; - } - - mFloatingTaskLayer = new FloatingTaskLayer(mContext, this, mWindowManager); - - WindowManager.LayoutParams params = new WindowManager.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE - | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL - | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, - PixelFormat.TRANSLUCENT - ); - params.setTrustedOverlay(); - params.setFitInsetsTypes(0); - params.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; - params.setTitle("FloatingTaskLayer"); - params.packageName = mContext.getPackageName(); - params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; - - try { - mIsFloatingLayerAdded = true; - mWindowManager.addView(mFloatingTaskLayer, params); - } catch (IllegalStateException e) { - // This means the floating layer has already been added which shouldn't happen. - e.printStackTrace(); - } - } - - private void removeFloatingLayer() { - if (!mIsFloatingLayerAdded) { - return; - } - try { - mIsFloatingLayerAdded = false; - if (mFloatingTaskLayer != null) { - mWindowManager.removeView(mFloatingTaskLayer); - } - } catch (IllegalArgumentException e) { - // This means the floating layer has already been removed which shouldn't happen. - e.printStackTrace(); - } - } - - /** - * Description of current floating task state. - */ - private void dump(PrintWriter pw, String prefix) { - pw.println("FloatingTaskController state:"); - pw.print(" isFloatingLayerAvailable= "); pw.println(isFloatingLayerAvailable()); - pw.print(" isFloatingTasksEnabled= "); pw.println(isFloatingTasksEnabled()); - pw.print(" mIsFloatingLayerAdded= "); pw.println(mIsFloatingLayerAdded); - pw.print(" mLastPosition= "); pw.println(mLastPosition); - pw.println(); - } - - /** Returns the {@link FloatingTasks} implementation. */ - public FloatingTasks asFloatingTasks() { - return mImpl; - } - - @Override - public Context getContext() { - return mContext; - } - - @Override - public ShellExecutor getRemoteCallExecutor() { - return mMainExecutor; - } - - /** - * The interface for calls from outside the shell, within the host process. - */ - @ExternalThread - private class FloatingTaskImpl implements FloatingTasks { - @Override - public void showOrSetStashed(Intent intent) { - mMainExecutor.execute(() -> FloatingTasksController.this.showOrSetStashed(intent)); - } - } - - /** - * The interface for calls from outside the host process. - */ - @BinderThread - private static class IFloatingTasksImpl extends IFloatingTasks.Stub - implements ExternalInterfaceBinder { - private FloatingTasksController mController; - - IFloatingTasksImpl(FloatingTasksController controller) { - mController = controller; - } - - /** - * Invalidates this instance, preventing future calls from updating the controller. - */ - @Override - public void invalidate() { - mController = null; - } - - public void showTask(Intent intent) { - executeRemoteCallWithTaskPermission(mController, "showTask", - (controller) -> controller.showTask(intent)); - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl deleted file mode 100644 index f79ca1039865d..0000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl +++ /dev/null @@ -1,28 +0,0 @@ -/* - * 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; - -/** - * Interface that is exposed to remote callers to manipulate floating task features. - */ -interface IFloatingTasks { - - void showTask(in Intent intent) = 1; - -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java deleted file mode 100644 index c922109751ba6..0000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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.views; - -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import com.android.wm.shell.R; - -/** - * Displays the menu items for a floating task view (e.g. close). - */ -public class FloatingMenuView extends LinearLayout { - - private int mItemSize; - private int mItemMargin; - - public FloatingMenuView(Context context) { - super(context); - setOrientation(LinearLayout.HORIZONTAL); - setGravity(Gravity.CENTER); - - mItemSize = context.getResources().getDimensionPixelSize( - R.dimen.floating_task_menu_item_size); - mItemMargin = context.getResources().getDimensionPixelSize( - R.dimen.floating_task_menu_item_padding); - } - - /** Adds a clickable item to the menu bar. Items are ordered as added. */ - public void addMenuItem(@Nullable Drawable drawable, View.OnClickListener listener) { - ImageView itemView = new ImageView(getContext()); - itemView.setScaleType(ImageView.ScaleType.CENTER); - if (drawable != null) { - itemView.setImageDrawable(drawable); - } - LinearLayout.LayoutParams lp = new LayoutParams(mItemSize, - ViewGroup.LayoutParams.MATCH_PARENT); - lp.setMarginStart(mItemMargin); - lp.setMarginEnd(mItemMargin); - addView(itemView, lp); - - itemView.setOnClickListener(listener); - } - - /** - * The menu extends past the top of the TaskView because of the rounded corners. This means - * to center content in the menu we must subtract the radius (i.e. the amount of space covered - * by TaskView). - */ - public void setCornerRadius(float radius) { - setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius); - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java deleted file mode 100644 index 16dab2415bf2c..0000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java +++ /dev/null @@ -1,687 +0,0 @@ -/* - * 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.views; - -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.annotation.Nullable; -import android.content.Context; -import android.graphics.Color; -import android.graphics.Insets; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.Region; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewPropertyAnimator; -import android.view.ViewTreeObserver; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.view.WindowMetrics; -import android.widget.FrameLayout; - -import androidx.annotation.NonNull; -import androidx.dynamicanimation.animation.DynamicAnimation; -import androidx.dynamicanimation.animation.FlingAnimation; - -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.R; -import com.android.wm.shell.floating.FloatingDismissController; -import com.android.wm.shell.floating.FloatingTasksController; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * This is the layout that {@link FloatingTaskView}s are contained in. It handles input and - * movement of the task views. - */ -public class FloatingTaskLayer extends FrameLayout - implements ViewTreeObserver.OnComputeInternalInsetsListener { - - private static final String TAG = FloatingTaskLayer.class.getSimpleName(); - - /** How big to make the task view based on screen width of the largest size. */ - private static final float START_SIZE_WIDTH_PERCENT = 0.33f; - /** Min fling velocity required to move the view from one side of the screen to the other. */ - private static final float ESCAPE_VELOCITY = 750f; - /** Amount of friction to apply to fling animations. */ - private static final float FLING_FRICTION = 1.9f; - - private final FloatingTasksController mController; - private final FloatingDismissController mDismissController; - private final WindowManager mWindowManager; - private final TouchHandlerImpl mTouchHandler; - - private final Region mTouchableRegion = new Region(); - private final Rect mPositionRect = new Rect(); - private final Point mDefaultStartPosition = new Point(); - private final Point mTaskViewSize = new Point(); - private WindowInsets mWindowInsets; - private int mVerticalPadding; - private int mOverhangWhenStashed; - - private final List mSystemGestureExclusionRects = Collections.singletonList(new Rect()); - private ViewTreeObserver.OnDrawListener mSystemGestureExclusionListener = - this::updateSystemGestureExclusion; - - /** Interface allowing something to handle the touch events going to a task. */ - interface FloatingTaskTouchHandler { - void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, - float viewInitialX, float viewInitialY); - - void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, - float dx, float dy); - - void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, - float dx, float dy, float velX, float velY); - - void onClick(@NonNull FloatingTaskView v); - } - - public FloatingTaskLayer(Context context, - FloatingTasksController controller, - WindowManager windowManager) { - super(context); - // TODO: Why is this necessary? Without it FloatingTaskView does not render correctly. - setBackgroundColor(Color.argb(0, 0, 0, 0)); - - mController = controller; - mWindowManager = windowManager; - updateSizes(); - - // TODO: Might make sense to put dismiss controller in the touch handler since that's the - // main user of dismiss controller. - mDismissController = new FloatingDismissController(context, mController, this); - mTouchHandler = new TouchHandlerImpl(); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - getViewTreeObserver().addOnComputeInternalInsetsListener(this); - getViewTreeObserver().addOnDrawListener(mSystemGestureExclusionListener); - setOnApplyWindowInsetsListener((view, windowInsets) -> { - if (!windowInsets.equals(mWindowInsets)) { - mWindowInsets = windowInsets; - updateSizes(); - } - return windowInsets; - }); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - getViewTreeObserver().removeOnComputeInternalInsetsListener(this); - getViewTreeObserver().removeOnDrawListener(mSystemGestureExclusionListener); - } - - @Override - public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) { - inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - mTouchableRegion.setEmpty(); - getTouchableRegion(mTouchableRegion); - inoutInfo.touchableRegion.set(mTouchableRegion); - } - - /** Adds a floating task to the layout. */ - public void addTask(FloatingTasksController.Task task) { - if (task.floatingView == null) return; - - task.floatingView.setTouchHandler(mTouchHandler); - addView(task.floatingView, new LayoutParams(mTaskViewSize.x, mTaskViewSize.y)); - updateTaskViewPosition(task.floatingView); - } - - /** Animates the stashed state of the provided task, if it's part of the floating layer. */ - public void setStashed(FloatingTasksController.Task task, boolean shouldStash) { - if (task.floatingView != null && task.floatingView.getParent() == this) { - mTouchHandler.stashTaskView(task.floatingView, shouldStash); - } - } - - /** Removes all {@link FloatingTaskView} from the layout. */ - public void removeAllTaskViews() { - int childCount = getChildCount(); - ArrayList viewsToRemove = new ArrayList<>(); - for (int i = 0; i < childCount; i++) { - if (getChildAt(i) instanceof FloatingTaskView) { - viewsToRemove.add(getChildAt(i)); - } - } - for (View v : viewsToRemove) { - removeView(v); - } - } - - /** Returns the number of task views in the layout. */ - public int getTaskViewCount() { - int taskViewCount = 0; - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - if (getChildAt(i) instanceof FloatingTaskView) { - taskViewCount++; - } - } - return taskViewCount; - } - - /** - * Called when the task view is un-stuck from the dismiss target. - * @param v the task view being moved. - * @param velX the x velocity of the motion event. - * @param velY the y velocity of the motion event. - * @param wasFlungOut true if the user flung the task view out of the dismiss target (i.e. there - * was an 'up' event), otherwise the user is still dragging. - */ - public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY, - boolean wasFlungOut) { - mTouchHandler.onUnstuckFromTarget(v, velX, velY, wasFlungOut); - } - - /** - * Updates dimensions and applies them to any task views. - */ - public void updateSizes() { - if (mDismissController != null) { - mDismissController.updateSizes(); - } - - mOverhangWhenStashed = getResources().getDimensionPixelSize( - R.dimen.floating_task_stash_offset); - mVerticalPadding = getResources().getDimensionPixelSize( - R.dimen.floating_task_vertical_padding); - - WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics(); - WindowInsets windowInsets = windowMetrics.getWindowInsets(); - Insets insets = windowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.navigationBars() - | WindowInsets.Type.statusBars() - | WindowInsets.Type.displayCutout()); - Rect bounds = windowMetrics.getBounds(); - mPositionRect.set(bounds.left + insets.left, - bounds.top + insets.top + mVerticalPadding, - bounds.right - insets.right, - bounds.bottom - insets.bottom - mVerticalPadding); - - int taskViewWidth = Math.max(bounds.height(), bounds.width()); - int taskViewHeight = Math.min(bounds.height(), bounds.width()); - taskViewHeight = taskViewHeight - (insets.top + insets.bottom + (mVerticalPadding * 2)); - mTaskViewSize.set((int) (taskViewWidth * START_SIZE_WIDTH_PERCENT), taskViewHeight); - mDefaultStartPosition.set(mPositionRect.left, mPositionRect.top); - - // Update existing views - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - if (getChildAt(i) instanceof FloatingTaskView) { - FloatingTaskView child = (FloatingTaskView) getChildAt(i); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - lp.width = mTaskViewSize.x; - lp.height = mTaskViewSize.y; - child.setLayoutParams(lp); - updateTaskViewPosition(child); - } - } - } - - /** Returns the first floating task view in the layout. (Currently only ever 1 view). */ - @Nullable - public FloatingTaskView getFirstTaskView() { - int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child instanceof FloatingTaskView) { - return (FloatingTaskView) child; - } - } - return null; - } - - private void updateTaskViewPosition(FloatingTaskView floatingView) { - Point lastPosition = mController.getLastPosition(); - if (lastPosition.x == -1 && lastPosition.y == -1) { - floatingView.setX(mDefaultStartPosition.x); - floatingView.setY(mDefaultStartPosition.y); - } else { - floatingView.setX(lastPosition.x); - floatingView.setY(lastPosition.y); - } - if (mTouchHandler.isStashedPosition(floatingView)) { - floatingView.setStashed(true); - } - floatingView.updateLocation(); - } - - /** - * Updates the area of the screen that shouldn't allow the back gesture due to the placement - * of task view (i.e. when task view is stashed on an edge, tapping or swiping that edge would - * un-stash the task view instead of performing the back gesture). - */ - private void updateSystemGestureExclusion() { - Rect excludeZone = mSystemGestureExclusionRects.get(0); - FloatingTaskView floatingTaskView = getFirstTaskView(); - if (floatingTaskView != null && floatingTaskView.isStashed()) { - excludeZone.set(floatingTaskView.getLeft(), - floatingTaskView.getTop(), - floatingTaskView.getRight(), - floatingTaskView.getBottom()); - excludeZone.offset((int) (floatingTaskView.getTranslationX()), - (int) (floatingTaskView.getTranslationY())); - setSystemGestureExclusionRects(mSystemGestureExclusionRects); - } else { - excludeZone.setEmpty(); - setSystemGestureExclusionRects(Collections.emptyList()); - } - } - - /** - * Fills in the touchable region for floating windows. This is used by WindowManager to - * decide which touch events go to the floating windows. - */ - private void getTouchableRegion(Region outRegion) { - int childCount = getChildCount(); - Rect temp = new Rect(); - for (int i = 0; i < childCount; i++) { - View child = getChildAt(i); - if (child instanceof FloatingTaskView) { - child.getBoundsOnScreen(temp); - outRegion.op(temp, Region.Op.UNION); - } - } - } - - /** - * Implementation of the touch handler. Animates the task view based on touch events. - */ - private class TouchHandlerImpl implements FloatingTaskTouchHandler { - /** - * The view can be stashed by swiping it towards the current edge or moving it there. If - * the view gets moved in a way that is not one of these gestures, this is flipped to false. - */ - private boolean mCanStash = true; - /** - * This is used to indicate that the view has been un-stuck from the dismiss target and - * needs to spring to the current touch location. - */ - // TODO: implement this behavior - private boolean mSpringToTouchOnNextMotionEvent = false; - - private ArrayList mFlingAnimations; - private ViewPropertyAnimator mViewPropertyAnimation; - - private float mViewInitialX; - private float mViewInitialY; - - private float[] mMinMax = new float[2]; - - @Override - public void onDown(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, float viewInitialX, - float viewInitialY) { - mCanStash = true; - mViewInitialX = viewInitialX; - mViewInitialY = viewInitialY; - mDismissController.setUpMagneticObject(v); - mDismissController.passEventToMagnetizedObject(ev); - } - - @Override - public void onMove(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, - float dx, float dy) { - // Shows the magnetic dismiss target if needed. - mDismissController.showDismiss(/* show= */ true); - - // Send it to magnetic target first. - if (mDismissController.passEventToMagnetizedObject(ev)) { - v.setStashed(false); - mCanStash = true; - - return; - } - - // If we're here magnetic target didn't want it so move as per normal. - - v.setTranslationX(capX(v, mViewInitialX + dx, /* isMoving= */ true)); - v.setTranslationY(capY(v, mViewInitialY + dy)); - if (v.isStashed()) { - // Check if we've moved far enough to be not stashed. - final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f); - final boolean viewInitiallyOnLeftSide = mViewInitialX < centerX; - if (viewInitiallyOnLeftSide) { - if (v.getTranslationX() > mPositionRect.left) { - v.setStashed(false); - mCanStash = true; - } - } else if (v.getTranslationX() + v.getWidth() < mPositionRect.right) { - v.setStashed(false); - mCanStash = true; - } - } - } - - // Reference for math / values: StackAnimationController#flingStackThenSpringToEdge. - // TODO clean up the code here, pretty hard to comprehend - // TODO code here doesn't work the best when in portrait (e.g. can't fling up/down on edges) - @Override - public void onUp(@NonNull FloatingTaskView v, @NonNull MotionEvent ev, - float dx, float dy, float velX, float velY) { - - // Send it to magnetic target first. - if (mDismissController.passEventToMagnetizedObject(ev)) { - v.setStashed(false); - return; - } - mDismissController.showDismiss(/* show= */ false); - - // If we're here magnetic target didn't want it so handle up as per normal. - - final float x = capX(v, mViewInitialX + dx, /* isMoving= */ false); - final float centerX = mPositionRect.centerX(); - final boolean viewInitiallyOnLeftSide = mViewInitialX + v.getWidth() < centerX; - final boolean viewOnLeftSide = x + v.getWidth() < centerX; - final boolean isFling = Math.abs(velX) > ESCAPE_VELOCITY; - final boolean isFlingLeft = isFling && velX < ESCAPE_VELOCITY; - // TODO: check velX here sometimes it doesn't stash on move when I think it should - final boolean shouldStashFromMove = - (velX < 0 && v.getTranslationX() < mPositionRect.left) - || (velX > 0 - && v.getTranslationX() + v.getWidth() > mPositionRect.right); - final boolean shouldStashFromFling = viewInitiallyOnLeftSide == viewOnLeftSide - && isFling - && ((viewOnLeftSide && velX < ESCAPE_VELOCITY) - || (!viewOnLeftSide && velX > ESCAPE_VELOCITY)); - final boolean shouldStash = mCanStash && (shouldStashFromFling || shouldStashFromMove); - - ProtoLog.d(WM_SHELL_FLOATING_APPS, - "shouldStash=%s shouldStashFromFling=%s shouldStashFromMove=%s" - + " viewInitiallyOnLeftSide=%s viewOnLeftSide=%s isFling=%s velX=%f" - + " isStashed=%s", shouldStash, shouldStashFromFling, shouldStashFromMove, - viewInitiallyOnLeftSide, viewOnLeftSide, isFling, velX, v.isStashed()); - - if (v.isStashed()) { - mMinMax[0] = viewOnLeftSide - ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed - : mPositionRect.right - v.getWidth(); - mMinMax[1] = viewOnLeftSide - ? mPositionRect.left - : mPositionRect.right - mOverhangWhenStashed; - } else { - populateMinMax(v, viewOnLeftSide, shouldStash, mMinMax); - } - - boolean movingLeft = isFling ? isFlingLeft : viewOnLeftSide; - float destinationRelativeX = movingLeft - ? mMinMax[0] - : mMinMax[1]; - - // TODO: why is this necessary / when does this happen? - if (mMinMax[1] < v.getTranslationX()) { - mMinMax[1] = v.getTranslationX(); - } - if (v.getTranslationX() < mMinMax[0]) { - mMinMax[0] = v.getTranslationX(); - } - - // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity - // so that it'll make it all the way to the side of the screen. - final float minimumVelocityToReachEdge = - getMinimumVelocityToReachEdge(v, destinationRelativeX); - final float startXVelocity = movingLeft - ? Math.min(minimumVelocityToReachEdge, velX) - : Math.max(minimumVelocityToReachEdge, velX); - - cancelAnyAnimations(v); - - mFlingAnimations = getAnimationForUpEvent(v, shouldStash, - startXVelocity, mMinMax[0], mMinMax[1], destinationRelativeX); - for (int i = 0; i < mFlingAnimations.size(); i++) { - mFlingAnimations.get(i).start(); - } - } - - @Override - public void onClick(@NonNull FloatingTaskView v) { - if (v.isStashed()) { - final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f); - final boolean viewOnLeftSide = v.getTranslationX() < centerX; - final float destinationRelativeX = viewOnLeftSide - ? mPositionRect.left - : mPositionRect.right - v.getWidth(); - final float minimumVelocityToReachEdge = - getMinimumVelocityToReachEdge(v, destinationRelativeX); - populateMinMax(v, viewOnLeftSide, /* stashed= */ true, mMinMax); - - cancelAnyAnimations(v); - - FlingAnimation flingAnimation = new FlingAnimation(v, - DynamicAnimation.TRANSLATION_X); - flingAnimation.setFriction(FLING_FRICTION) - .setStartVelocity(minimumVelocityToReachEdge) - .setMinValue(mMinMax[0]) - .setMaxValue(mMinMax[1]) - .addEndListener((animation, canceled, value, velocity) -> { - if (canceled) return; - mController.setLastPosition((int) v.getTranslationX(), - (int) v.getTranslationY()); - v.setStashed(false); - v.updateLocation(); - }); - mFlingAnimations = new ArrayList<>(); - mFlingAnimations.add(flingAnimation); - flingAnimation.start(); - } - } - - public void onUnstuckFromTarget(FloatingTaskView v, float velX, float velY, - boolean wasFlungOut) { - if (wasFlungOut) { - snapTaskViewToEdge(v, velX, /* shouldStash= */ false); - } else { - // TODO: use this for something / to spring the view to the touch location - mSpringToTouchOnNextMotionEvent = true; - } - } - - public void stashTaskView(FloatingTaskView v, boolean shouldStash) { - if (v.isStashed() == shouldStash) { - return; - } - final float centerX = mPositionRect.centerX() - (v.getWidth() / 2f); - final boolean viewOnLeftSide = v.getTranslationX() < centerX; - snapTaskViewToEdge(v, viewOnLeftSide ? -ESCAPE_VELOCITY : ESCAPE_VELOCITY, shouldStash); - } - - public boolean isStashedPosition(View v) { - return v.getTranslationX() < mPositionRect.left - || v.getTranslationX() + v.getWidth() > mPositionRect.right; - } - - // TODO: a lot of this is duplicated in onUp -- can it be unified? - private void snapTaskViewToEdge(FloatingTaskView v, float velX, boolean shouldStash) { - final boolean movingLeft = velX < ESCAPE_VELOCITY; - populateMinMax(v, movingLeft, shouldStash, mMinMax); - float destinationRelativeX = movingLeft - ? mMinMax[0] - : mMinMax[1]; - - // TODO: why is this necessary / when does this happen? - if (mMinMax[1] < v.getTranslationX()) { - mMinMax[1] = v.getTranslationX(); - } - if (v.getTranslationX() < mMinMax[0]) { - mMinMax[0] = v.getTranslationX(); - } - - // Use the touch event's velocity if it's sufficient, otherwise use the minimum velocity - // so that it'll make it all the way to the side of the screen. - final float minimumVelocityToReachEdge = - getMinimumVelocityToReachEdge(v, destinationRelativeX); - final float startXVelocity = movingLeft - ? Math.min(minimumVelocityToReachEdge, velX) - : Math.max(minimumVelocityToReachEdge, velX); - - cancelAnyAnimations(v); - - mFlingAnimations = getAnimationForUpEvent(v, - shouldStash, startXVelocity, mMinMax[0], mMinMax[1], - destinationRelativeX); - for (int i = 0; i < mFlingAnimations.size(); i++) { - mFlingAnimations.get(i).start(); - } - } - - private void cancelAnyAnimations(FloatingTaskView v) { - if (mFlingAnimations != null) { - for (int i = 0; i < mFlingAnimations.size(); i++) { - if (mFlingAnimations.get(i).isRunning()) { - mFlingAnimations.get(i).cancel(); - } - } - } - if (mViewPropertyAnimation != null) { - mViewPropertyAnimation.cancel(); - mViewPropertyAnimation = null; - } - } - - private ArrayList getAnimationForUpEvent(FloatingTaskView v, - boolean shouldStash, float startVelX, float minValue, float maxValue, - float destinationRelativeX) { - final float ty = v.getTranslationY(); - final ArrayList animations = new ArrayList<>(); - if (ty != capY(v, ty)) { - // The view was being dismissed so the Y is out of bounds, need to animate that. - FlingAnimation yFlingAnimation = new FlingAnimation(v, - DynamicAnimation.TRANSLATION_Y); - yFlingAnimation.setFriction(FLING_FRICTION) - .setStartVelocity(startVelX) - .setMinValue(mPositionRect.top) - .setMaxValue(mPositionRect.bottom - mTaskViewSize.y); - animations.add(yFlingAnimation); - } - FlingAnimation flingAnimation = new FlingAnimation(v, DynamicAnimation.TRANSLATION_X); - flingAnimation.setFriction(FLING_FRICTION) - .setStartVelocity(startVelX) - .setMinValue(minValue) - .setMaxValue(maxValue) - .addEndListener((animation, canceled, value, velocity) -> { - if (canceled) return; - Runnable endAction = () -> { - v.setStashed(shouldStash); - v.updateLocation(); - if (!v.isStashed()) { - mController.setLastPosition((int) v.getTranslationX(), - (int) v.getTranslationY()); - } - }; - if (!shouldStash) { - final int xTranslation = (int) v.getTranslationX(); - if (xTranslation != destinationRelativeX) { - // TODO: this animation doesn't feel great, should figure out - // a better way to do this or remove the need for it all together. - mViewPropertyAnimation = v.animate() - .translationX(destinationRelativeX) - .setListener(getAnimationListener(endAction)); - mViewPropertyAnimation.start(); - } else { - endAction.run(); - } - } else { - endAction.run(); - } - }); - animations.add(flingAnimation); - return animations; - } - - private AnimatorListenerAdapter getAnimationListener(Runnable endAction) { - return new AnimatorListenerAdapter() { - boolean translationCanceled = false; - @Override - public void onAnimationCancel(Animator animation) { - super.onAnimationCancel(animation); - translationCanceled = true; - } - - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - if (!translationCanceled) { - endAction.run(); - } - } - }; - } - - private void populateMinMax(FloatingTaskView v, boolean onLeft, boolean shouldStash, - float[] out) { - if (shouldStash) { - out[0] = onLeft - ? mPositionRect.left - v.getWidth() + mOverhangWhenStashed - : mPositionRect.right - v.getWidth(); - out[1] = onLeft - ? mPositionRect.left - : mPositionRect.right - mOverhangWhenStashed; - } else { - out[0] = mPositionRect.left; - out[1] = mPositionRect.right - mTaskViewSize.x; - } - } - - private float getMinimumVelocityToReachEdge(FloatingTaskView v, - float destinationRelativeX) { - // Minimum velocity required for the view to make it to the targeted side of the screen, - // taking friction into account (4.2f is the number that friction scalars are multiplied - // by in DynamicAnimation.DragForce). This is an estimate and could be slightly off, the - // animation at the end will ensure that it reaches the destination X regardless. - return (destinationRelativeX - v.getTranslationX()) * (FLING_FRICTION * 4.2f); - } - - private float capX(FloatingTaskView v, float x, boolean isMoving) { - final int width = v.getWidth(); - if (v.isStashed() || isMoving) { - if (x < mPositionRect.left - v.getWidth() + mOverhangWhenStashed) { - return mPositionRect.left - v.getWidth() + mOverhangWhenStashed; - } - if (x > mPositionRect.right - mOverhangWhenStashed) { - return mPositionRect.right - mOverhangWhenStashed; - } - } else { - if (x < mPositionRect.left) { - return mPositionRect.left; - } - if (x > mPositionRect.right - width) { - return mPositionRect.right - width; - } - } - return x; - } - - private float capY(FloatingTaskView v, float y) { - final int height = v.getHeight(); - if (y < mPositionRect.top) { - return mPositionRect.top; - } - if (y > mPositionRect.bottom - height) { - return mPositionRect.bottom - height; - } - return y; - } - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java deleted file mode 100644 index 581204a82ec7e..0000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * 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.views; - -import static android.app.ActivityTaskManager.INVALID_TASK_ID; -import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK; -import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT; - -import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_FLOATING_APPS; - -import android.app.ActivityManager; -import android.app.ActivityOptions; -import android.app.ActivityTaskManager; -import android.app.PendingIntent; -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.Outline; -import android.graphics.Rect; -import android.os.RemoteException; -import android.util.Log; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewOutlineProvider; -import android.widget.FrameLayout; - -import androidx.annotation.NonNull; - -import com.android.internal.policy.ScreenDecorationsUtils; -import com.android.internal.protolog.common.ProtoLog; -import com.android.wm.shell.R; -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.TaskView; -import com.android.wm.shell.TaskViewTransitions; -import com.android.wm.shell.bubbles.RelativeTouchListener; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.common.annotations.ShellMainThread; -import com.android.wm.shell.floating.FloatingTasksController; - -/** - * A view that holds a floating task using {@link TaskView} along with additional UI to manage - * the task. - */ -public class FloatingTaskView extends FrameLayout { - - private static final String TAG = FloatingTaskView.class.getSimpleName(); - - private FloatingTasksController mController; - - private FloatingMenuView mMenuView; - private int mMenuHeight; - private TaskView mTaskView; - - private float mCornerRadius = 0f; - private int mBackgroundColor; - - private FloatingTasksController.Task mTask; - - private boolean mIsStashed; - - /** - * Creates a floating task view. - * - * @param context the context to use. - * @param controller the controller to notify about changes in the floating task (e.g. removal). - */ - public FloatingTaskView(Context context, FloatingTasksController controller) { - super(context); - mController = controller; - setElevation(getResources().getDimensionPixelSize(R.dimen.floating_task_elevation)); - mMenuHeight = context.getResources().getDimensionPixelSize(R.dimen.floating_task_menu_size); - mMenuView = new FloatingMenuView(context); - addView(mMenuView); - - applyThemeAttrs(); - - setClipToOutline(true); - setOutlineProvider(new ViewOutlineProvider() { - @Override - public void getOutline(View view, Outline outline) { - outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius); - } - }); - } - - // TODO: call this when theme/config changes - void applyThemeAttrs() { - boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows( - mContext.getResources()); - final TypedArray ta = mContext.obtainStyledAttributes(new int[] { - android.R.attr.dialogCornerRadius, - android.R.attr.colorBackgroundFloating}); - mCornerRadius = supportsRoundedCorners ? ta.getDimensionPixelSize(0, 0) : 0; - mCornerRadius = mCornerRadius / 2f; - mBackgroundColor = ta.getColor(1, Color.WHITE); - - ta.recycle(); - - mMenuView.setCornerRadius(mCornerRadius); - mMenuHeight = getResources().getDimensionPixelSize( - R.dimen.floating_task_menu_size); - - if (mTaskView != null) { - mTaskView.setCornerRadius(mCornerRadius); - } - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - int height = MeasureSpec.getSize(heightMeasureSpec); - - // Add corner radius here so that the menu extends behind the rounded corners of TaskView. - int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height); - measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight, - MeasureSpec.getMode(heightMeasureSpec))); - - if (mTaskView != null) { - int taskViewHeight = height - menuViewHeight; - measureChild(mTaskView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(taskViewHeight, - MeasureSpec.getMode(heightMeasureSpec))); - } - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - // Drag handle above - final int dragHandleBottom = t + mMenuView.getMeasuredHeight(); - mMenuView.layout(l, t, r, dragHandleBottom); - if (mTaskView != null) { - // Subtract radius so that the menu extends behind the rounded corners of TaskView. - mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r, - dragHandleBottom + mTaskView.getMeasuredHeight()); - } - } - - /** - * Constructs the TaskView to display the task. Must be called for {@link #startTask} to work. - */ - public void createTaskView(Context context, ShellTaskOrganizer organizer, - TaskViewTransitions transitions, SyncTransactionQueue syncQueue) { - mTaskView = new TaskView(context, organizer, transitions, syncQueue); - addView(mTaskView); - mTaskView.setEnableSurfaceClipping(true); - mTaskView.setCornerRadius(mCornerRadius); - } - - /** - * Starts the provided task in the TaskView, if the TaskView exists. This should be called after - * {@link #createTaskView}. - */ - public void startTask(@ShellMainThread ShellExecutor executor, - FloatingTasksController.Task task) { - if (mTaskView == null) { - Log.e(TAG, "starting task before creating the view!"); - return; - } - mTask = task; - mTaskView.setListener(executor, mTaskViewListener); - } - - /** - * Sets the touch handler for the view. - * - * @param handler the touch handler for the view. - */ - public void setTouchHandler(FloatingTaskLayer.FloatingTaskTouchHandler handler) { - setOnTouchListener(new RelativeTouchListener() { - @Override - public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) { - handler.onDown(FloatingTaskView.this, ev, v.getTranslationX(), v.getTranslationY()); - return true; - } - - @Override - public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, - float viewInitialY, float dx, float dy) { - handler.onMove(FloatingTaskView.this, ev, dx, dy); - } - - @Override - public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX, - float viewInitialY, float dx, float dy, float velX, float velY) { - handler.onUp(FloatingTaskView.this, ev, dx, dy, velX, velY); - } - }); - setOnClickListener(view -> { - handler.onClick(FloatingTaskView.this); - }); - - mMenuView.addMenuItem(null, view -> { - if (mIsStashed) { - // If we're stashed all clicks un-stash. - handler.onClick(FloatingTaskView.this); - } - }); - } - - private void setContentVisibility(boolean visible) { - if (mTaskView == null) return; - mTaskView.setAlpha(visible ? 1f : 0f); - } - - /** - * Sets the alpha of both this view and the TaskView. - */ - public void setTaskViewAlpha(float alpha) { - if (mTaskView != null) { - mTaskView.setAlpha(alpha); - } - setAlpha(alpha); - } - - /** - * Call when the location or size of the view has changed to update TaskView. - */ - public void updateLocation() { - if (mTaskView == null) return; - mTaskView.onLocationChanged(); - } - - private void updateMenuColor() { - ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo(); - int color = info != null ? info.taskDescription.getBackgroundColor() : -1; - if (color != -1) { - mMenuView.setBackgroundColor(color); - } else { - mMenuView.setBackgroundColor(mBackgroundColor); - } - } - - /** - * Sets whether the view is stashed or not. - * - * Also updates the touchable area based on this. If the view is stashed we don't direct taps - * on the activity to the activity, instead a tap will un-stash the view. - */ - public void setStashed(boolean isStashed) { - if (mIsStashed != isStashed) { - mIsStashed = isStashed; - if (mTaskView == null) { - return; - } - updateObscuredTouchRect(); - } - } - - /** Whether the view is stashed at the edge of the screen or not. **/ - public boolean isStashed() { - return mIsStashed; - } - - private void updateObscuredTouchRect() { - if (mIsStashed) { - Rect tmpRect = new Rect(); - getBoundsOnScreen(tmpRect); - mTaskView.setObscuredTouchRect(tmpRect); - } else { - mTaskView.setObscuredTouchRect(null); - } - } - - /** - * Whether the task needs to be restarted, this can happen when {@link #cleanUpTaskView()} has - * been called on this view or if - * {@link #startTask(ShellExecutor, FloatingTasksController.Task)} was never called. - */ - public boolean needsTaskStarted() { - // If the task needs to be restarted then TaskView would have been cleaned up. - return mTaskView == null; - } - - /** Call this when the floating task activity is no longer in use. */ - public void cleanUpTaskView() { - if (mTask != null && mTask.taskId != INVALID_TASK_ID) { - try { - ActivityTaskManager.getService().removeTask(mTask.taskId); - } catch (RemoteException e) { - Log.e(TAG, e.getMessage()); - } - } - if (mTaskView != null) { - mTaskView.release(); - removeView(mTaskView); - mTaskView = null; - } - } - - // TODO: use task background colour / how to get the taskInfo ? - private static int getDragBarColor(ActivityManager.RunningTaskInfo taskInfo) { - final int taskBgColor = taskInfo.taskDescription.getStatusBarColor(); - return Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb(); - } - - private final TaskView.Listener mTaskViewListener = new TaskView.Listener() { - private boolean mInitialized = false; - private boolean mDestroyed = false; - - @Override - public void onInitialized() { - if (mDestroyed || mInitialized) { - return; - } - // Custom options so there is no activity transition animation - ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(), - /* enterResId= */ 0, /* exitResId= */ 0); - - Rect launchBounds = new Rect(); - mTaskView.getBoundsOnScreen(launchBounds); - - try { - options.setTaskAlwaysOnTop(true); - if (mTask.intent != null) { - Intent fillInIntent = new Intent(); - // Apply flags to make behaviour match documentLaunchMode=always. - fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); - fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); - - PendingIntent pi = PendingIntent.getActivity(mContext, 0, mTask.intent, - PendingIntent.FLAG_MUTABLE, - null); - mTaskView.startActivity(pi, fillInIntent, options, launchBounds); - } else { - ProtoLog.e(WM_SHELL_FLOATING_APPS, "Tried to start a task with null intent"); - } - } catch (RuntimeException e) { - ProtoLog.e(WM_SHELL_FLOATING_APPS, "Exception while starting task: %s", - e.getMessage()); - mController.removeTask(); - } - mInitialized = true; - } - - @Override - public void onReleased() { - mDestroyed = true; - } - - @Override - public void onTaskCreated(int taskId, ComponentName name) { - mTask.taskId = taskId; - updateMenuColor(); - setContentVisibility(true); - } - - @Override - public void onTaskVisibilityChanged(int taskId, boolean visible) { - setContentVisibility(visible); - } - - @Override - public void onTaskRemovalStarted(int taskId) { - // Must post because this is called from a binder thread. - post(() -> { - mController.removeTask(); - cleanUpTaskView(); - }); - } - - @Override - public void onBackPressedOnTaskRoot(int taskId) { - if (mTask.taskId == taskId && !mIsStashed) { - // TODO: is removing the window the desired behavior? - post(() -> mController.removeTask()); - } - } - }; -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java deleted file mode 100644 index d378a177650ad..0000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java +++ /dev/null @@ -1,261 +0,0 @@ -/* - * 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 static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; -import static com.android.wm.shell.floating.FloatingTasksController.SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET; - -import static com.google.common.truth.Truth.assertThat; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Intent; -import android.content.res.Configuration; -import android.graphics.Insets; -import android.graphics.Rect; -import android.os.RemoteException; -import android.os.SystemProperties; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.view.WindowMetrics; - -import androidx.test.filters.SmallTest; -import androidx.test.runner.AndroidJUnit4; - -import com.android.wm.shell.ShellTaskOrganizer; -import com.android.wm.shell.ShellTestCase; -import com.android.wm.shell.TaskViewTransitions; -import com.android.wm.shell.TestShellExecutor; -import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.common.SyncTransactionQueue; -import com.android.wm.shell.floating.views.FloatingTaskLayer; -import com.android.wm.shell.sysui.ShellCommandHandler; -import com.android.wm.shell.sysui.ShellController; -import com.android.wm.shell.sysui.ShellInit; -import com.android.wm.shell.sysui.ShellSharedConstants; - -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -import java.util.Optional; - -/** - * Tests for the floating tasks controller. - */ -@SmallTest -@RunWith(AndroidJUnit4.class) -public class FloatingTasksControllerTest extends ShellTestCase { - // Some behavior in the controller constructor is dependent on this so we can only - // validate if it's working for the real value for those things. - private static final boolean FLOATING_TASKS_ACTUALLY_ENABLED = - SystemProperties.getBoolean("persist.wm.debug.floating_tasks", false); - - @Mock private ShellInit mShellInit; - @Mock private ShellController mShellController; - @Mock private WindowManager mWindowManager; - @Mock private ShellTaskOrganizer mTaskOrganizer; - @Captor private ArgumentCaptor mFloatingTaskLayerCaptor; - - private FloatingTasksController mController; - - @Before - public void setUp() throws RemoteException { - MockitoAnnotations.initMocks(this); - - WindowMetrics windowMetrics = mock(WindowMetrics.class); - WindowInsets windowInsets = mock(WindowInsets.class); - Insets insets = Insets.of(0, 0, 0, 0); - when(mWindowManager.getCurrentWindowMetrics()).thenReturn(windowMetrics); - when(windowMetrics.getWindowInsets()).thenReturn(windowInsets); - when(windowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1000, 1000)); - when(windowInsets.getInsetsIgnoringVisibility(anyInt())).thenReturn(insets); - - // For the purposes of this test, just run everything synchronously - ShellExecutor shellExecutor = new TestShellExecutor(); - when(mTaskOrganizer.getExecutor()).thenReturn(shellExecutor); - } - - @After - public void tearDown() { - if (mController != null) { - mController.removeTask(); - mController = null; - } - } - - private void setUpTabletConfig() { - Configuration config = mock(Configuration.class); - config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET; - mController.setConfig(config); - } - - private void setUpPhoneConfig() { - Configuration config = mock(Configuration.class); - config.smallestScreenWidthDp = SMALLEST_SCREEN_WIDTH_DP_TO_BE_TABLET - 1; - mController.setConfig(config); - } - - private void createController() { - mController = new FloatingTasksController(mContext, - mShellInit, - mShellController, - mock(ShellCommandHandler.class), - Optional.empty(), - mWindowManager, - mTaskOrganizer, - mock(TaskViewTransitions.class), - mock(ShellExecutor.class), - mock(ShellExecutor.class), - mock(SyncTransactionQueue.class)); - spyOn(mController); - } - - // - // Shell specific - // - @Test - public void instantiateController_addInitCallback() { - if (FLOATING_TASKS_ACTUALLY_ENABLED) { - createController(); - setUpTabletConfig(); - - verify(mShellInit, times(1)).addInitCallback(any(), any()); - } - } - - @Test - public void instantiateController_doesntAddInitCallback() { - if (!FLOATING_TASKS_ACTUALLY_ENABLED) { - createController(); - - verify(mShellInit, never()).addInitCallback(any(), any()); - } - } - - @Test - public void onInit_registerConfigChangeListener() { - if (FLOATING_TASKS_ACTUALLY_ENABLED) { - createController(); - setUpTabletConfig(); - mController.onInit(); - - verify(mShellController, times(1)).addConfigurationChangeListener(any()); - } - } - - @Test - public void onInit_addExternalInterface() { - if (FLOATING_TASKS_ACTUALLY_ENABLED) { - createController(); - setUpTabletConfig(); - mController.onInit(); - - verify(mShellController, times(1)).addExternalInterface( - ShellSharedConstants.KEY_EXTRA_SHELL_FLOATING_TASKS, any(), any()); - } - } - - // - // Tests for floating layer, which is only available for tablets. - // - - @Test - public void testIsFloatingLayerAvailable_true() { - createController(); - setUpTabletConfig(); - assertThat(mController.isFloatingLayerAvailable()).isTrue(); - } - - @Test - public void testIsFloatingLayerAvailable_false() { - createController(); - setUpPhoneConfig(); - assertThat(mController.isFloatingLayerAvailable()).isFalse(); - } - - // - // Tests for floating tasks being enabled, guarded by sysprop flag. - // - - @Test - public void testIsFloatingTasksEnabled_true() { - createController(); - mController.setFloatingTasksEnabled(true); - setUpTabletConfig(); - assertThat(mController.isFloatingTasksEnabled()).isTrue(); - } - - @Test - public void testIsFloatingTasksEnabled_false() { - createController(); - mController.setFloatingTasksEnabled(false); - setUpTabletConfig(); - assertThat(mController.isFloatingTasksEnabled()).isFalse(); - } - - // - // Tests for behavior depending on flags - // - - @Test - public void testShowTaskIntent_enabled() { - createController(); - mController.setFloatingTasksEnabled(true); - setUpTabletConfig(); - - mController.showTask(mock(Intent.class)); - verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any()); - assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1); - } - - @Test - public void testShowTaskIntent_notEnabled() { - createController(); - mController.setFloatingTasksEnabled(false); - setUpTabletConfig(); - - mController.showTask(mock(Intent.class)); - verify(mWindowManager, never()).addView(any(), any()); - } - - @Test - public void testRemoveTask() { - createController(); - mController.setFloatingTasksEnabled(true); - setUpTabletConfig(); - - mController.showTask(mock(Intent.class)); - verify(mWindowManager).addView(mFloatingTaskLayerCaptor.capture(), any()); - assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(1); - - mController.removeTask(); - verify(mWindowManager).removeView(mFloatingTaskLayerCaptor.capture()); - assertThat(mFloatingTaskLayerCaptor.getValue().getTaskViewCount()).isEqualTo(0); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java index a21f45f701b34..632fcdc162592 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java @@ -97,7 +97,6 @@ public abstract class SystemUIInitializer { .setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper()) .setRecentTasks(mWMComponent.getRecentTasks()) .setBackAnimation(mWMComponent.getBackAnimation()) - .setFloatingTasks(mWMComponent.getFloatingTasks()) .setDesktopMode(mWMComponent.getDesktopMode()); // Only initialize when not starting from tests since this currently initializes some @@ -118,7 +117,6 @@ public abstract class SystemUIInitializer { .setStartingSurface(Optional.ofNullable(null)) .setRecentTasks(Optional.ofNullable(null)) .setBackAnimation(Optional.ofNullable(null)) - .setFloatingTasks(Optional.ofNullable(null)) .setDesktopMode(Optional.ofNullable(null)); } mSysUIComponent = builder.build(); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index d05bd5120872a..c260b7df2b453 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -40,7 +40,6 @@ import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.bubbles.Bubbles; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; -import com.android.wm.shell.floating.FloatingTasks; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasks; @@ -110,9 +109,6 @@ public interface SysUIComponent { @BindsInstance Builder setBackAnimation(Optional b); - @BindsInstance - Builder setFloatingTasks(Optional f); - @BindsInstance Builder setDesktopMode(Optional d); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index e46bdb7a8ad8d..95919c6b2c0df 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -41,6 +41,7 @@ import com.android.systemui.demomode.dagger.DemoModeModule; import com.android.systemui.doze.dagger.DozeComponent; import com.android.systemui.dreams.dagger.DreamModule; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.FlagsModule; import com.android.systemui.fragments.FragmentService; import com.android.systemui.keyguard.data.BouncerViewModule; @@ -242,6 +243,7 @@ public abstract class SystemUIModule { CommonNotifCollection notifCollection, NotifPipeline notifPipeline, SysUiState sysUiState, + FeatureFlags featureFlags, @Main Executor sysuiMainExecutor) { return Optional.ofNullable(BubblesManager.create(context, bubblesOptional, @@ -258,6 +260,7 @@ public abstract class SystemUIModule { notifCollection, notifPipeline, sysUiState, + featureFlags, sysuiMainExecutor)); } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java index 096f969493826..d756f3a446557 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java @@ -32,7 +32,6 @@ import com.android.wm.shell.dagger.WMShellModule; import com.android.wm.shell.dagger.WMSingleton; import com.android.wm.shell.desktopmode.DesktopMode; import com.android.wm.shell.displayareahelper.DisplayAreaHelper; -import com.android.wm.shell.floating.FloatingTasks; import com.android.wm.shell.onehanded.OneHanded; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.recents.RecentTasks; @@ -111,9 +110,6 @@ public interface WMComponent { @WMSingleton Optional getBackAnimation(); - @WMSingleton - Optional getFloatingTasks(); - /** * Optional {@link DesktopMode} component for interacting with desktop mode. */ diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 974f6058a1a36..f51e281966272 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -284,15 +284,6 @@ object Flags { @Keep val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false) - @JvmField - @Keep - val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false) - - @JvmField - @Keep - val SHOW_FLOATING_TASKS_AS_BUBBLES = - SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false) - @JvmField @Keep val ENABLE_FLING_TO_DISMISS_BUBBLE = @@ -308,6 +299,9 @@ object Flags { val ENABLE_PIP_KEEP_CLEAR_ALGORITHM = SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false) + // TODO(b/256873975): Tracking Bug + @JvmField @Keep val WM_BUBBLE_BAR = UnreleasedFlag(1111) + // 1200 - predictive back @JvmField @Keep diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index d247f249e2fde..b964b76795b8e 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -22,7 +22,7 @@ import android.os.UserManager import android.view.KeyEvent import com.android.systemui.dagger.SysUISingleton import com.android.systemui.util.kotlin.getOrNull -import com.android.wm.shell.floating.FloatingTasks +import com.android.wm.shell.bubbles.Bubbles import java.util.Optional import javax.inject.Inject @@ -39,7 +39,7 @@ internal class NoteTaskController constructor( private val context: Context, private val intentResolver: NoteTaskIntentResolver, - private val optionalFloatingTasks: Optional, + private val optionalBubbles: Optional, private val optionalKeyguardManager: Optional, private val optionalUserManager: Optional, @NoteTaskEnabledKey private val isEnabled: Boolean, @@ -54,7 +54,7 @@ constructor( } private fun showNoteTask() { - val floatingTasks = optionalFloatingTasks.getOrNull() ?: return + val bubbles = optionalBubbles.getOrNull() ?: return val keyguardManager = optionalKeyguardManager.getOrNull() ?: return val userManager = optionalUserManager.getOrNull() ?: return val intent = intentResolver.resolveIntent() ?: return @@ -66,7 +66,7 @@ constructor( context.startActivity(intent) } else { // TODO(b/254606432): Should include Intent.EXTRA_FLOATING_WINDOW_MODE parameter. - floatingTasks.showOrSetStashed(intent) + bubbles.showAppBubble(intent) } } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt index d84717da3a219..0a5b6008981b5 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskInitializer.kt @@ -17,7 +17,7 @@ package com.android.systemui.notetask import com.android.systemui.statusbar.CommandQueue -import com.android.wm.shell.floating.FloatingTasks +import com.android.wm.shell.bubbles.Bubbles import dagger.Lazy import java.util.Optional import javax.inject.Inject @@ -26,7 +26,7 @@ import javax.inject.Inject internal class NoteTaskInitializer @Inject constructor( - private val optionalFloatingTasks: Optional, + private val optionalBubbles: Optional, private val lazyNoteTaskController: Lazy, private val commandQueue: CommandQueue, @NoteTaskEnabledKey private val isEnabled: Boolean, @@ -40,7 +40,7 @@ constructor( } fun initialize() { - if (isEnabled && optionalFloatingTasks.isPresent) { + if (isEnabled && optionalBubbles.isPresent) { commandQueue.addCallback(callbacks) } } diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java index 4e77514645645..a4384d5810ce7 100644 --- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java +++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java @@ -25,6 +25,7 @@ import static android.service.notification.NotificationListenerService.REASON_GR import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE; import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; +import static com.android.systemui.flags.Flags.WM_BUBBLE_BAR; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; @@ -51,6 +52,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.model.SysUiState; import com.android.systemui.shade.ShadeController; import com.android.systemui.shared.system.QuickStepContract; @@ -129,6 +131,7 @@ public class BubblesManager { CommonNotifCollection notifCollection, NotifPipeline notifPipeline, SysUiState sysUiState, + FeatureFlags featureFlags, Executor sysuiMainExecutor) { if (bubblesOptional.isPresent()) { return new BubblesManager(context, @@ -146,6 +149,7 @@ public class BubblesManager { notifCollection, notifPipeline, sysUiState, + featureFlags, sysuiMainExecutor); } else { return null; @@ -168,6 +172,7 @@ public class BubblesManager { CommonNotifCollection notifCollection, NotifPipeline notifPipeline, SysUiState sysUiState, + FeatureFlags featureFlags, Executor sysuiMainExecutor) { mContext = context; mBubbles = bubbles; @@ -352,6 +357,7 @@ public class BubblesManager { }); } }; + mBubbles.setBubbleBarEnabled(featureFlags.isEnabled(WM_BUBBLE_BAR)); mBubbles.setSysuiProxy(mSysuiProxy); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index f20c6a29b8406..9758842d1e353 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -25,8 +25,8 @@ import androidx.test.runner.AndroidJUnit4 import com.android.systemui.SysuiTestCase import com.android.systemui.notetask.NoteTaskIntentResolver.Companion.NOTES_ACTION import com.android.systemui.util.mockito.whenever -import com.android.wm.shell.floating.FloatingTasks -import java.util.* +import com.android.wm.shell.bubbles.Bubbles +import java.util.Optional import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -49,8 +49,8 @@ internal class NoteTaskControllerTest : SysuiTestCase() { @Mock lateinit var context: Context @Mock lateinit var noteTaskIntentResolver: NoteTaskIntentResolver - @Mock lateinit var floatingTasks: FloatingTasks - @Mock lateinit var optionalFloatingTasks: Optional + @Mock lateinit var bubbles: Bubbles + @Mock lateinit var optionalBubbles: Optional @Mock lateinit var keyguardManager: KeyguardManager @Mock lateinit var optionalKeyguardManager: Optional @Mock lateinit var optionalUserManager: Optional @@ -61,7 +61,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) whenever(noteTaskIntentResolver.resolveIntent()).thenReturn(notesIntent) - whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks) + whenever(optionalBubbles.orElse(null)).thenReturn(bubbles) whenever(optionalKeyguardManager.orElse(null)).thenReturn(keyguardManager) whenever(optionalUserManager.orElse(null)).thenReturn(userManager) whenever(userManager.isUserUnlocked).thenReturn(true) @@ -71,7 +71,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { return NoteTaskController( context = context, intentResolver = noteTaskIntentResolver, - optionalFloatingTasks = optionalFloatingTasks, + optionalBubbles = optionalBubbles, optionalKeyguardManager = optionalKeyguardManager, optionalUserManager = optionalUserManager, isEnabled = isEnabled, @@ -85,16 +85,16 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test - fun handleSystemKey_keyguardIsUnlocked_shouldStartFloatingTask() { + fun handleSystemKey_keyguardIsUnlocked_shouldStartBubbles() { whenever(keyguardManager.isKeyguardLocked).thenReturn(false) createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) - verify(floatingTasks).showOrSetStashed(notesIntent) + verify(bubbles).showAppBubble(notesIntent) verify(context, never()).startActivity(notesIntent) } @@ -103,17 +103,17 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_UNKNOWN) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test - fun handleSystemKey_floatingTasksIsNull_shouldDoNothing() { - whenever(optionalFloatingTasks.orElse(null)).thenReturn(null) + fun handleSystemKey_bubblesIsNull_shouldDoNothing() { + whenever(optionalBubbles.orElse(null)).thenReturn(null) createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test @@ -123,7 +123,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test @@ -133,7 +133,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test @@ -143,7 +143,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test @@ -151,7 +151,7 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController(isEnabled = false).handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } @Test @@ -161,6 +161,6 @@ internal class NoteTaskControllerTest : SysuiTestCase() { createNoteTaskController().handleSystemKey(KeyEvent.KEYCODE_VIDEO_APP_1) verify(context, never()).startActivity(notesIntent) - verify(floatingTasks, never()).showOrSetStashed(notesIntent) + verify(bubbles, never()).showAppBubble(notesIntent) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt index f344c8d9eec47..334089c43e270 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskInitializerTest.kt @@ -21,8 +21,8 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.statusbar.CommandQueue import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever -import com.android.wm.shell.floating.FloatingTasks -import java.util.* +import com.android.wm.shell.bubbles.Bubbles +import java.util.Optional import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -43,20 +43,20 @@ import org.mockito.MockitoAnnotations internal class NoteTaskInitializerTest : SysuiTestCase() { @Mock lateinit var commandQueue: CommandQueue - @Mock lateinit var floatingTasks: FloatingTasks - @Mock lateinit var optionalFloatingTasks: Optional + @Mock lateinit var bubbles: Bubbles + @Mock lateinit var optionalBubbles: Optional @Before fun setUp() { MockitoAnnotations.initMocks(this) - whenever(optionalFloatingTasks.isPresent).thenReturn(true) - whenever(optionalFloatingTasks.orElse(null)).thenReturn(floatingTasks) + whenever(optionalBubbles.isPresent).thenReturn(true) + whenever(optionalBubbles.orElse(null)).thenReturn(bubbles) } private fun createNoteTaskInitializer(isEnabled: Boolean = true): NoteTaskInitializer { return NoteTaskInitializer( - optionalFloatingTasks = optionalFloatingTasks, + optionalBubbles = optionalBubbles, lazyNoteTaskController = mock(), commandQueue = commandQueue, isEnabled = isEnabled, @@ -78,8 +78,8 @@ internal class NoteTaskInitializerTest : SysuiTestCase() { } @Test - fun initialize_floatingTasksNotPresent_shouldDoNothing() { - whenever(optionalFloatingTasks.isPresent).thenReturn(false) + fun initialize_bubblesNotPresent_shouldDoNothing() { + whenever(optionalBubbles.isPresent).thenReturn(false) createNoteTaskInitializer().initialize() diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index fa7ebf6a2449f..bee882d438496 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -86,6 +86,7 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; import com.android.systemui.colorextraction.SysuiColorExtractor; import com.android.systemui.dump.DumpManager; +import com.android.systemui.flags.FeatureFlags; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -394,6 +395,7 @@ public class BubblesTest extends SysuiTestCase { mCommonNotifCollection, mNotifPipeline, mSysUiState, + mock(FeatureFlags.class), syncExecutor); mBubblesManager.addNotifCallback(mNotifCallback);