From 46faccde4a0157cb5b2fc2469bc6fe2d317dfe13 Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Mon, 31 Oct 2022 16:21:19 -0700 Subject: [PATCH 1/2] Remove FloatingTaskController & replace usages with Bubbles Switch to using newly exposed method Bubbles#showAppBubble as what FloatingTaskController does is no longer POR. Calling this allows 1 bubble from a specific app to be added to the stack. The only user of this method is NoteTaskController which is guarded by the NOTE_TASK. Bug: 237678727 Test: treehugger Change-Id: I47c5c010a050796eff848ba3fcabc40cf5fbd902 --- .../wm/shell/bubbles/BubbleController.java | 47 +- .../com/android/wm/shell/bubbles/Bubbles.java | 10 + .../wm/shell/dagger/WMShellBaseModule.java | 44 -- .../floating/FloatingDismissController.java | 259 ------- .../wm/shell/floating/FloatingTasks.java | 36 - .../floating/FloatingTasksController.java | 454 ------------ .../wm/shell/floating/IFloatingTasks.aidl | 28 - .../floating/views/FloatingMenuView.java | 73 -- .../floating/views/FloatingTaskLayer.java | 687 ------------------ .../floating/views/FloatingTaskView.java | 385 ---------- .../floating/FloatingTasksControllerTest.java | 261 ------- .../android/systemui/SystemUIInitializer.java | 2 - .../systemui/dagger/SysUIComponent.java | 4 - .../android/systemui/dagger/WMComponent.java | 4 - .../src/com/android/systemui/flags/Flags.kt | 9 - .../systemui/notetask/NoteTaskController.kt | 8 +- .../systemui/notetask/NoteTaskInitializer.kt | 6 +- .../notetask/NoteTaskControllerTest.kt | 36 +- .../notetask/NoteTaskInitializerTest.kt | 18 +- 19 files changed, 73 insertions(+), 2298 deletions(-) delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingDismissController.java delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasks.java delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/floating/FloatingTasksController.java delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/floating/IFloatingTasks.aidl delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingMenuView.java delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskLayer.java delete mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/floating/views/FloatingTaskView.java delete mode 100644 libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/floating/FloatingTasksControllerTest.java 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..0596be5783ddf 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; @@ -591,11 +591,6 @@ public class BubbleController implements ConfigurationChangeListener { } mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation); } - if (SHOW_FLOATING_TASKS_AS_BUBBLES && mBubblePositioner.isLargeScreen()) { - mBubblePositioner.setUsePinnedLocation(true); - } else { - mBubblePositioner.setUsePinnedLocation(false); - } addToWindowManagerMaybe(); } @@ -959,14 +954,18 @@ public class BubbleController implements ConfigurationChangeListener { } /** - * Adds a bubble for a specific intent. These bubbles are 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 +1488,23 @@ public class BubbleController implements ConfigurationChangeListener { } PackageManager packageManager = getPackageManagerForUser( context, entry.getStatusBarNotification().getUser().getIdentifier()); - ActivityInfo info = - intent.getIntent().resolveActivityInfo(packageManager, 0); + return isResizableActivity(intent.getIntent(), packageManager, entry.getKey()); + } + + static boolean isResizableActivity(Intent intent, PackageManager packageManager, String key) { + if (intent == null) { + Log.w(TAG, "Unable to send as bubble: " + key + " null intent"); + return false; + } + ActivityInfo info = intent.resolveActivityInfo(packageManager, 0); if (info == null) { - Log.w(TAG, "Unable to send as bubble, " - + entry.getKey() + " couldn't find activity info for intent: " - + intent); + Log.w(TAG, "Unable to send as bubble: " + key + + " couldn't find activity info for intent: " + intent); return false; } if (!ActivityInfo.isResizeableMode(info.resizeMode)) { - Log.w(TAG, "Unable to send as bubble, " - + entry.getKey() + " activity is not resizable for intent: " - + intent); + Log.w(TAG, "Unable to send as bubble: " + key + + " activity is not resizable for intent: " + intent); return false; } return true; @@ -1673,6 +1677,13 @@ public class BubbleController implements ConfigurationChangeListener { }); } + @Override + public void showAppBubble(Intent intent) { + mMainExecutor.execute(() -> { + BubbleController.this.showAppBubble(intent); + }); + } + @Override public boolean handleDismissalInterception(BubbleEntry entry, @Nullable List children, IntConsumer removeCallback, 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..bb240193c7c48 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. */ 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/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 844a311a988b9..82894461b63ec 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -268,15 +268,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 = 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/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() From 6188843762982aa9c9e533b93f51db77083814c7 Mon Sep 17 00:00:00 2001 From: Mady Mellor Date: Tue, 1 Nov 2022 13:25:41 -0700 Subject: [PATCH 2/2] Add a flag for the bubble bar This is an UnreleasedFlag which is only available in SysUI, so we set it on Bubbles via BubblesManager which is part of SysUI. Changes to support bubble bar will happen behind this flag. Bug: 253318833 Bug: 256873975 Test: manual - enable flag and add a bubble on a large screen device - observe that the bubbles are pinned to the bottom Change-Id: I0d86a71b66db683837bf639d9067d92703b4364e --- .../wm/shell/bubbles/BubbleController.java | 23 ++++++++++++++++++- .../com/android/wm/shell/bubbles/Bubbles.java | 5 ++++ .../systemui/dagger/SystemUIModule.java | 3 +++ .../src/com/android/systemui/flags/Flags.kt | 3 +++ .../systemui/wmshell/BubblesManager.java | 6 +++++ .../android/systemui/wmshell/BubblesTest.java | 2 ++ 6 files changed, 41 insertions(+), 1 deletion(-) 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 0596be5783ddf..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 @@ -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 @@ -592,6 +600,12 @@ public class BubbleController implements ConfigurationChangeListener { mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation); } + if (mIsBubbleBarEnabled && mBubblePositioner.isLargeScreen()) { + mBubblePositioner.setUsePinnedLocation(true); + } else { + mBubblePositioner.setUsePinnedLocation(false); + } + addToWindowManagerMaybe(); } @@ -1794,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 bb240193c7c48..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 @@ -242,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/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 6db562107357e..12d2846ddd231 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; @@ -240,6 +241,7 @@ public abstract class SystemUIModule { CommonNotifCollection notifCollection, NotifPipeline notifPipeline, SysUiState sysUiState, + FeatureFlags featureFlags, @Main Executor sysuiMainExecutor) { return Optional.ofNullable(BubblesManager.create(context, bubblesOptional, @@ -256,6 +258,7 @@ public abstract class SystemUIModule { notifCollection, notifPipeline, sysUiState, + featureFlags, sysuiMainExecutor)); } diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 82894461b63ec..be203e361a6ba 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -283,6 +283,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/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/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);