diff options
21 files changed, 1142 insertions, 785 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 529af2297ef1..57d8dc70cd88 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -15,8 +15,8 @@ */ package com.android.systemui.bubbles; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.os.AsyncTask.Status.FINISHED; -import static android.view.Display.INVALID_DISPLAY; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; @@ -256,7 +256,8 @@ class Bubble implements BubbleViewProvider { } /** - * Cleanup expanded view for bubbles going into overflow. + * Call this to clean up the task for the bubble. Ensure this is always called when done with + * the bubble. */ void cleanupExpandedView() { if (mExpandedView != null) { @@ -270,8 +271,7 @@ class Bubble implements BubbleViewProvider { } /** - * Call when the views should be removed, ensure this is called to clean up ActivityView - * content. + * Call when all the views should be removed/cleaned up. */ void cleanupViews() { cleanupExpandedView(); @@ -468,14 +468,6 @@ class Bubble implements BubbleViewProvider { return mIntentActive; } - /** - * @return the display id of the virtual display on which bubble contents is drawn. - */ - @Override - public int getDisplayId() { - return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY; - } - public InstanceId getInstanceId() { return mInstanceId; } @@ -490,6 +482,14 @@ class Bubble implements BubbleViewProvider { } /** + * @return the task id of the task in which bubble contents is drawn. + */ + @Override + public int getTaskId() { + return mExpandedView != null ? mExpandedView.getTaskId() : INVALID_TASK_ID; + } + + /** * Should be invoked whenever a Bubble is accessed (selected while expanded). */ void markAsAccessedAt(long lastAccessedMillis) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index 289fd9c02921..3f94b00d3c60 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -16,6 +16,7 @@ package com.android.systemui.bubbles; +import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.Notification.FLAG_BUBBLE; import static android.app.NotificationManager.BUBBLE_PREFERENCE_NONE; import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED; @@ -27,8 +28,6 @@ import static android.service.notification.NotificationListenerService.REASON_CL import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED; import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE; import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL; -import static android.view.Display.DEFAULT_DISPLAY; -import static android.view.Display.INVALID_DISPLAY; import static android.view.View.INVISIBLE; import static android.view.View.VISIBLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; @@ -46,6 +45,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityTaskManager; import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; @@ -70,7 +70,6 @@ import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseSetArray; -import android.view.Display; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; @@ -85,6 +84,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.internal.statusbar.NotificationVisibility; import com.android.systemui.Dumpable; import com.android.systemui.bubbles.dagger.BubbleModule; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -113,6 +113,7 @@ import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.phone.StatusBar; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.pip.PinnedStackListenerForwarder; @@ -171,7 +172,7 @@ public class BubbleController implements Bubbles, ConfigurationController.Config private final FloatingContentCoordinator mFloatingContentCoordinator; private final BubbleDataRepository mDataRepository; private BubbleLogger mLogger; - + private final Handler mMainHandler; private BubbleData mBubbleData; private ScrimView mBubbleScrim; @Nullable private BubbleStackView mStackView; @@ -243,6 +244,8 @@ public class BubbleController implements Bubbles, ConfigurationController.Config private boolean mInflateSynchronously; + private MultiWindowTaskListener mTaskListener; + // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline private final List<NotifCallback> mCallbacks = new ArrayList<>(); @@ -373,7 +376,9 @@ public class BubbleController implements Bubbles, ConfigurationController.Config WindowManager windowManager, WindowManagerShellWrapper windowManagerShellWrapper, LauncherApps launcherApps, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + @Main Handler mainHandler, + ShellTaskOrganizer organizer) { BubbleLogger logger = new BubbleLogger(uiEventLogger); return new BubbleController(context, notificationShadeWindowController, statusBarStateController, shadeController, new BubbleData(context, logger), @@ -381,7 +386,8 @@ public class BubbleController implements Bubbles, ConfigurationController.Config notifUserManager, groupManager, entryManager, notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, new BubbleDataRepository(context, launcherApps), sysUiState, notificationManager, - statusBarService, windowManager, windowManagerShellWrapper, launcherApps, logger); + statusBarService, windowManager, windowManagerShellWrapper, launcherApps, logger, + mainHandler, organizer); } /** @@ -411,7 +417,9 @@ public class BubbleController implements Bubbles, ConfigurationController.Config WindowManager windowManager, WindowManagerShellWrapper windowManagerShellWrapper, LauncherApps launcherApps, - BubbleLogger bubbleLogger) { + BubbleLogger bubbleLogger, + Handler mainHandler, + ShellTaskOrganizer organizer) { dumpManager.registerDumpable(TAG, this); mContext = context; mShadeController = shadeController; @@ -422,6 +430,7 @@ public class BubbleController implements Bubbles, ConfigurationController.Config mDataRepository = dataRepository; mINotificationManager = notificationManager; mLogger = bubbleLogger; + mMainHandler = mainHandler; mZenModeController.addCallback(new ZenModeController.Callback() { @Override public void onZenChanged(int zen) { @@ -520,6 +529,7 @@ public class BubbleController implements Bubbles, ConfigurationController.Config }); mBubbleIconFactory = new BubbleIconFactory(context); + mTaskListener = new MultiWindowTaskListener(mMainHandler, organizer); launcherApps.registerCallback(new LauncherApps.Callback() { @Override @@ -794,6 +804,11 @@ public class BubbleController implements Bubbles, ConfigurationController.Config return mBubbleData.getOverflowBubbles(); } + @Override + public MultiWindowTaskListener getTaskManager() { + return mTaskListener; + } + /** * BubbleStackView is lazily created by this method the first time a Bubble is added. This * method initializes the stack view and adds it to the StatusBar just above the scrim. @@ -836,9 +851,8 @@ public class BubbleController implements Bubbles, ConfigurationController.Config ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, - // Start not focusable - we'll become focusable when expanded so the ActivityView - // can use the IME. WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, PixelFormat.TRANSLUCENT); @@ -854,16 +868,13 @@ public class BubbleController implements Bubbles, ConfigurationController.Config mAddedToWindowManager = true; mWindowManager.addView(mStackView, mWmLayoutParams); } catch (IllegalStateException e) { - // This means the stack has already been added. This shouldn't happen, since we keep - // track of that, but just in case, update the previously added view's layout params. + // This means the stack has already been added. This shouldn't happen... e.printStackTrace(); - updateWmFlags(); } } private void onImeVisibilityChanged(boolean imeVisible) { mImeVisible = imeVisible; - updateWmFlags(); } /** Removes the BubbleStackView from the WindowManager if it's there. */ @@ -890,35 +901,6 @@ public class BubbleController implements Bubbles, ConfigurationController.Config } /** - * Updates the BubbleStackView's WindowManager.LayoutParams, and updates the WindowManager with - * the new params if the stack has been added. - */ - private void updateWmFlags() { - if (mStackView == null) { - return; - } - if (isStackExpanded() && !mImeVisible) { - // If we're expanded, and the IME isn't visible, we want to be focusable. This ensures - // that any taps within Bubbles (including on the ActivityView) results in Bubbles - // receiving focus and clearing it from any other windows that might have it. - mWmLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - } else { - // If we're collapsed, we don't want to be focusable since tapping on the stack would - // steal focus from apps. We also don't want to be focusable if the IME is visible, - mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; - } - - if (mAddedToWindowManager) { - try { - mWindowManager.updateViewLayout(mStackView, mWmLayoutParams); - } catch (IllegalArgumentException e) { - // If the stack is somehow not there, ignore the attempt to update it. - e.printStackTrace(); - } - } - } - - /** * Called by the BubbleStackView and whenever all bubbles have animated out, and none have been * added in the meantime. */ @@ -1026,8 +1008,6 @@ public class BubbleController implements Bubbles, ConfigurationController.Config if (listener != null) { listener.onBubbleExpandChanged(isExpanding, key); } - - updateWmFlags(); }); if (mStackView != null) { mStackView.setExpandListener(mExpandListener); @@ -1071,7 +1051,7 @@ public class BubbleController implements Bubbles, ConfigurationController.Config @Override public boolean isBubbleExpanded(NotificationEntry entry) { return isStackExpanded() && mBubbleData != null && mBubbleData.getSelectedBubble() != null - && mBubbleData.getSelectedBubble().getKey().equals(entry.getKey()) ? true : false; + && mBubbleData.getSelectedBubble().getKey().equals(entry.getKey()); } @Override @@ -1126,13 +1106,6 @@ public class BubbleController implements Bubbles, ConfigurationController.Config } } - @Override - public void performBackPressIfNeeded() { - if (mStackView != null) { - mStackView.performBackPressIfNeeded(); - } - } - /** * Adds or updates a bubble associated with the provided notification entry. * @@ -1613,19 +1586,21 @@ public class BubbleController implements Bubbles, ConfigurationController.Config mStackView.updateContentDescription(); } - @Override - public int getExpandedDisplayId(Context context) { + /** + * The task id of the expanded view, if the stack is expanded and not occluded by the + * status bar, otherwise returns {@link ActivityTaskManager#INVALID_TASK_ID}. + */ + private int getExpandedTaskId() { if (mStackView == null) { - return INVALID_DISPLAY; + return INVALID_TASK_ID; } - final boolean defaultDisplay = context.getDisplay() != null - && context.getDisplay().getDisplayId() == DEFAULT_DISPLAY; final BubbleViewProvider expandedViewProvider = mStackView.getExpandedBubble(); - if (defaultDisplay && expandedViewProvider != null && isStackExpanded() + if (expandedViewProvider != null && isStackExpanded() + && !mStackView.isExpansionAnimating() && !mNotificationShadeWindowController.getPanelExpanded()) { - return expandedViewProvider.getDisplayId(); + return expandedViewProvider.getTaskId(); } - return INVALID_DISPLAY; + return INVALID_TASK_ID; } @VisibleForTesting @@ -1659,18 +1634,17 @@ public class BubbleController implements Bubbles, ConfigurationController.Config @Override public void onTaskMovedToFront(RunningTaskInfo taskInfo) { - if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) { - if (!mStackView.isExpansionAnimating()) { - mBubbleData.setExpanded(false); - } + int expandedId = getExpandedTaskId(); + if (expandedId != INVALID_TASK_ID && expandedId != taskInfo.taskId) { + mBubbleData.setExpanded(false); } } @Override - public void onActivityRestartAttempt(RunningTaskInfo task, boolean homeTaskVisible, + public void onActivityRestartAttempt(RunningTaskInfo taskInfo, boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { for (Bubble b : mBubbleData.getBubbles()) { - if (b.getDisplayId() == task.displayId) { + if (taskInfo.taskId == b.getTaskId()) { mBubbleData.setSelectedBubble(b); mBubbleData.setExpanded(true); return; @@ -1678,43 +1652,6 @@ public class BubbleController implements Bubbles, ConfigurationController.Config } } - @Override - public void onActivityLaunchOnSecondaryDisplayRerouted() { - if (mStackView != null) { - mBubbleData.setExpanded(false); - } - } - - @Override - public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { - if (mStackView != null && taskInfo.displayId == getExpandedDisplayId(mContext)) { - if (mImeVisible) { - hideCurrentInputMethod(); - } else { - mBubbleData.setExpanded(false); - } - } - } - - @Override - public void onSingleTaskDisplayDrawn(int displayId) { - if (mStackView == null) { - return; - } - mStackView.showExpandedViewContents(displayId); - } - - @Override - public void onSingleTaskDisplayEmpty(int displayId) { - final BubbleViewProvider expandedBubble = mStackView != null - ? mStackView.getExpandedBubble() - : null; - int expandedId = expandedBubble != null ? expandedBubble.getDisplayId() : -1; - if (mStackView != null && mStackView.isExpanded() && expandedId == displayId) { - mBubbleData.setExpanded(false); - } - mBubbleData.notifyDisplayEmpty(displayId); - } } /** @@ -1758,6 +1695,7 @@ public class BubbleController implements Bubbles, ConfigurationController.Config } /** PinnedStackListener that dispatches IME visibility updates to the stack. */ + //TODO(b/170442945): Better way to do this / insets listener? private class BubblesImeListener extends PinnedStackListenerForwarder.PinnedStackListener { @Override public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java index 693b4f007f73..b4626f27d370 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java @@ -551,22 +551,6 @@ public class BubbleData { dispatchPendingChanges(); } - /** - * Indicates that the provided display is no longer in use and should be cleaned up. - * - * @param displayId the id of the display to clean up. - */ - void notifyDisplayEmpty(int displayId) { - for (Bubble b : mBubbles) { - if (b.getDisplayId() == displayId) { - if (b.getExpandedView() != null) { - b.getExpandedView().notifyDisplayEmpty(); - } - return; - } - } - } - private void dispatchPendingChanges() { if (mListener != null && mStateChange.anythingChanged()) { mListener.applyUpdate(mStateChange); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 3af36a99374e..98a2257d2daa 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -16,16 +16,10 @@ package com.android.systemui.bubbles; +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 android.graphics.PixelFormat.TRANSPARENT; -import static android.view.Display.INVALID_DISPLAY; -import static android.view.InsetsState.ITYPE_IME; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; -import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; -import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; @@ -34,10 +28,7 @@ import static com.android.systemui.bubbles.BubbleOverflowActivity.EXTRA_BUBBLE_C import android.annotation.NonNull; import android.annotation.SuppressLint; -import android.app.ActivityManager; import android.app.ActivityOptions; -import android.app.ActivityTaskManager; -import android.app.ActivityView; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; @@ -50,19 +41,13 @@ import android.graphics.Outline; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.ShapeDrawable; -import android.hardware.display.VirtualDisplay; -import android.os.Binder; import android.os.Bundle; -import android.os.RemoteException; import android.util.AttributeSet; import android.util.Log; -import android.view.Gravity; import android.view.SurfaceControl; -import android.view.SurfaceView; import android.view.View; import android.view.ViewGroup; import android.view.ViewOutlineProvider; -import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; @@ -84,18 +69,6 @@ import java.io.PrintWriter; */ public class BubbleExpandedView extends LinearLayout { private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES; - private static final String WINDOW_TITLE = "ImeInsetsWindowWithoutContent"; - - private enum ActivityViewStatus { - // ActivityView is being initialized, cannot start an activity yet. - INITIALIZING, - // ActivityView is initialized, and ready to start an activity. - INITIALIZED, - // Activity runs in the ActivityView. - ACTIVITY_STARTED, - // ActivityView is released, so activity launching will no longer be permitted. - RELEASED, - } // The triangle pointing to the expanded view private View mPointerView; @@ -103,16 +76,11 @@ public class BubbleExpandedView extends LinearLayout { @Nullable private int[] mExpandedViewContainerLocation; private AlphaOptimizedButton mSettingsIcon; + private TaskView mTaskView; - // Views for expanded state - private ActivityView mActivityView; - - private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING; - private int mTaskId = -1; - - private PendingIntent mPendingIntent; + private int mTaskId = INVALID_TASK_ID; - private boolean mKeyboardVisible; + private boolean mImeVisible; private boolean mNeedsNewHeight; private Point mDisplaySize; @@ -123,123 +91,104 @@ public class BubbleExpandedView extends LinearLayout { private int mPointerHeight; private ShapeDrawable mPointerDrawable; private int mExpandedViewPadding; - + private float mCornerRadius = 0f; @Nullable private Bubble mBubble; + private PendingIntent mPendingIntent; private boolean mIsOverflow; private Bubbles mBubbles = Dependency.get(Bubbles.class); private WindowManager mWindowManager; - private ActivityManager mActivityManager; - private BubbleStackView mStackView; - private View mVirtualImeView; - private WindowManager mVirtualDisplayWindowManager; - private boolean mImeShowing = false; - private float mCornerRadius = 0f; /** * Container for the ActivityView that has a solid, round-rect background that shows if the * ActivityView hasn't loaded. */ - private FrameLayout mActivityViewContainer = new FrameLayout(getContext()); + private final FrameLayout mExpandedViewContainer = new FrameLayout(getContext()); - /** The SurfaceView that the ActivityView draws to. */ - @Nullable private SurfaceView mActivitySurface; + private final TaskView.Listener mTaskViewListener = new TaskView.Listener() { + private boolean mInitialized = false; + private boolean mDestroyed = false; - private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() { @Override - public void onActivityViewReady(ActivityView view) { + public void onInitialized() { if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "onActivityViewReady: mActivityViewStatus=" + mActivityViewStatus + Log.d(TAG, "onActivityViewReady: destroyed=" + mDestroyed + + " initialized=" + mInitialized + " bubble=" + getBubbleKey()); } - switch (mActivityViewStatus) { - case INITIALIZING: - case INITIALIZED: - // Custom options so there is no activity transition animation - ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(), - 0 /* enterResId */, 0 /* exitResId */); - options.setTaskAlwaysOnTop(true); - // Soptions.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); - // Post to keep the lifecycle normal - post(() -> { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "onActivityViewReady: calling startActivity, " - + "bubble=" + getBubbleKey()); - } - if (mActivityView == null) { - mBubbles.removeBubble(getBubbleKey(), - BubbleController.DISMISS_INVALID_INTENT); - return; - } - try { - if (!mIsOverflow && mBubble.hasMetadataShortcutId() - && mBubble.getShortcutInfo() != null) { - options.setApplyActivityFlagsForBubbles(true); - mActivityView.startShortcutActivity(mBubble.getShortcutInfo(), - options, null /* sourceBounds */); - } else { - Intent fillInIntent = new Intent(); - // Apply flags to make behaviour match documentLaunchMode=always. - fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); - fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); - if (mBubble != null) { - mBubble.setIntentActive(); - } - mActivityView.startActivity(mPendingIntent, fillInIntent, options); - } - } catch (RuntimeException e) { - // If there's a runtime exception here then there's something - // wrong with the intent, we can't really recover / try to populate - // the bubble again so we'll just remove it. - Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey() - + ", " + e.getMessage() + "; removing bubble"); - mBubbles.removeBubble(getBubbleKey(), - BubbleController.DISMISS_INVALID_INTENT); - } - }); - mActivityViewStatus = ActivityViewStatus.ACTIVITY_STARTED; - break; - case ACTIVITY_STARTED: - post(() -> mActivityManager.moveTaskToFront(mTaskId, 0)); - break; + + if (mDestroyed || mInitialized) { + return; } + // Custom options so there is no activity transition animation + ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(), + 0 /* enterResId */, 0 /* exitResId */); + + // TODO: I notice inconsistencies in lifecycle + // Post to keep the lifecycle normal + post(() -> { + if (DEBUG_BUBBLE_EXPANDED_VIEW) { + Log.d(TAG, "onActivityViewReady: calling startActivity, bubble=" + + getBubbleKey()); + } + try { + if (!mIsOverflow && mBubble.hasMetadataShortcutId()) { + mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), + options, null /* sourceBounds */); + } else { + Intent fillInIntent = new Intent(); + // Apply flags to make behaviour match documentLaunchMode=always. + fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT); + fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK); + if (mBubble != null) { + mBubble.setIntentActive(); + } + mTaskView.startActivity(mPendingIntent, fillInIntent, options); + } + } catch (RuntimeException e) { + // If there's a runtime exception here then there's something + // wrong with the intent, we can't really recover / try to populate + // the bubble again so we'll just remove it. + Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey() + + ", " + e.getMessage() + "; removing bubble"); + mBubbles.removeBubble(getBubbleKey(), + BubbleController.DISMISS_INVALID_INTENT); + } + }); + mInitialized = true; } @Override - public void onActivityViewDestroyed(ActivityView view) { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "onActivityViewDestroyed: mActivityViewStatus=" + mActivityViewStatus - + " bubble=" + getBubbleKey()); - } - mActivityViewStatus = ActivityViewStatus.RELEASED; + public void onReleased() { + mDestroyed = true; } @Override - public void onTaskCreated(int taskId, ComponentName componentName) { + public void onTaskCreated(int taskId, ComponentName name) { if (DEBUG_BUBBLE_EXPANDED_VIEW) { Log.d(TAG, "onTaskCreated: taskId=" + taskId + " bubble=" + getBubbleKey()); } - // Since Bubble ActivityView applies singleTaskDisplay this is - // guaranteed to only be called once per ActivityView. The taskId is - // saved to use for removeTask, preventing appearance in recent tasks. + // The taskId is saved to use for removeTask, preventing appearance in recent tasks. mTaskId = taskId; + + // With the task org, the taskAppeared callback will only happen once the task has + // already drawn + setContentVisibility(true); + } + + @Override + public void onTaskVisibilityChanged(int taskId, boolean visible) { + setContentVisibility(visible); } - /** - * This is only called for tasks on this ActivityView, which is also set to - * single-task mode -- meaning never more than one task on this display. If a task - * is being removed, it's the top Activity finishing and this bubble should - * be removed or collapsed. - */ @Override public void onTaskRemovalStarted(int taskId) { if (DEBUG_BUBBLE_EXPANDED_VIEW) { Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId - + " mActivityViewStatus=" + mActivityViewStatus + " bubble=" + getBubbleKey()); } if (mBubble != null) { @@ -248,6 +197,13 @@ public class BubbleExpandedView extends LinearLayout { BubbleController.DISMISS_TASK_FINISHED)); } } + + @Override + public void onBackPressedOnTaskRoot(int taskId) { + if (mTaskId == taskId && mStackView.isExpanded()) { + mBubbles.collapseStack(); + } + } }; public BubbleExpandedView(Context context) { @@ -266,7 +222,6 @@ public class BubbleExpandedView extends LinearLayout { int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); updateDimensions(); - mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); } void updateDimensions() { @@ -284,9 +239,6 @@ public class BubbleExpandedView extends LinearLayout { @Override protected void onFinishInflate() { super.onFinishInflate(); - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "onFinishInflate: bubble=" + getBubbleKey()); - } Resources res = getResources(); mPointerView = findViewById(R.id.pointer_view); @@ -301,35 +253,21 @@ public class BubbleExpandedView extends LinearLayout { R.dimen.bubble_manage_button_height); mSettingsIcon = findViewById(R.id.settings_button); - mActivityView = new ActivityView.Builder(mContext) - .setSingleInstance(true) - .setDisableSurfaceViewBackgroundLayer(true) - .setUseTrustedDisplay(true) - .build(); - + mTaskView = new TaskView(mContext, mBubbles.getTaskManager()); // Set ActivityView's alpha value as zero, since there is no view content to be shown. setContentVisibility(false); - mActivityViewContainer.setOutlineProvider(new ViewOutlineProvider() { + mExpandedViewContainer.setOutlineProvider(new ViewOutlineProvider() { @Override public void getOutline(View view, Outline outline) { outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius); } }); - mActivityViewContainer.setClipToOutline(true); - mActivityViewContainer.addView(mActivityView); - mActivityViewContainer.setLayoutParams( + mExpandedViewContainer.setClipToOutline(true); + mExpandedViewContainer.addView(mTaskView); + mExpandedViewContainer.setLayoutParams( new ViewGroup.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - addView(mActivityViewContainer); - - if (mActivityView != null - && mActivityView.getChildCount() > 0 - && mActivityView.getChildAt(0) instanceof SurfaceView) { - // Retrieve the surface from the ActivityView so we can screenshot it and change its - // z-ordering. This should always be possible, since ActivityView's constructor adds the - // SurfaceView as its first child. - mActivitySurface = (SurfaceView) mActivityView.getChildAt(0); - } + addView(mExpandedViewContainer); // Expanded stack layout, top to bottom: // Expanded view container @@ -337,33 +275,22 @@ public class BubbleExpandedView extends LinearLayout { // ==> expanded view // ==> activity view // ==> manage button - bringChildToFront(mActivityView); + bringChildToFront(mTaskView); bringChildToFront(mSettingsIcon); + mTaskView.setListener(mTaskViewListener); applyThemeAttrs(); - setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> { - // Keep track of IME displaying because we should not make any adjustments that might - // cause a config change while the IME is displayed otherwise it'll loose focus. - final int keyboardHeight = insets.getSystemWindowInsetBottom() - - insets.getStableInsetBottom(); - mKeyboardVisible = keyboardHeight != 0; - if (!mKeyboardVisible && mNeedsNewHeight) { - updateHeight(); - } - return view.onApplyWindowInsets(insets); - }); - mExpandedViewPadding = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_padding); setPadding(mExpandedViewPadding, mExpandedViewPadding, mExpandedViewPadding, mExpandedViewPadding); setOnTouchListener((view, motionEvent) -> { - if (!usingActivityView()) { + if (mTaskView == null) { return false; } final Rect avBounds = new Rect(); - mActivityView.getBoundsOnScreen(avBounds); + mTaskView.getBoundsOnScreen(avBounds); // Consume and ignore events on the expanded view padding that are within the // ActivityView's vertical bounds. These events are part of a back gesture, and so they @@ -389,51 +316,58 @@ public class BubbleExpandedView extends LinearLayout { } /** - * Asks the ActivityView's surface to draw on top of all other views in the window. This is - * useful for ordering surfaces during animations, but should otherwise be set to false so that - * bubbles and menus can draw over the ActivityView. + * Sets whether the surface displaying app content should sit on top. This is useful for + * ordering surfaces during animations. When content is drawn on top of the app (e.g. bubble + * being dragged out, the manage menu) this is set to false, otherwise it should be true. */ void setSurfaceZOrderedOnTop(boolean onTop) { - if (mActivitySurface == null) { + if (mTaskView == null) { return; } + mTaskView.setZOrderedOnTop(onTop, true /* allowDynamicChange */); + } - mActivitySurface.setZOrderedOnTop(onTop, true); + void setImeVisible(boolean visible) { + mImeVisible = visible; + if (!mImeVisible && mNeedsNewHeight) { + updateHeight(); + } } - /** Return a GraphicBuffer with the contents of the ActivityView's underlying surface. */ + /** Return a GraphicBuffer with the contents of the task view surface. */ @Nullable SurfaceControl.ScreenshotHardwareBuffer snapshotActivitySurface() { - if (mActivitySurface == null) { + if (mTaskView == null) { return null; } - return SurfaceControl.captureLayers( - mActivitySurface.getSurfaceControl(), - new Rect(0, 0, mActivityView.getWidth(), mActivityView.getHeight()), + mTaskView.getSurfaceControl(), + new Rect(0, 0, mTaskView.getWidth(), mTaskView.getHeight()), 1 /* scale */); } - int[] getActivityViewLocationOnScreen() { - if (mActivityView != null) { - return mActivityView.getLocationOnScreen(); + int[] getTaskViewLocationOnScreen() { + if (mTaskView != null) { + return mTaskView.getLocationOnScreen(); } else { return new int[]{0, 0}; } } + // TODO: Could listener be passed when we pass StackView / can we avoid setting this like this void setManageClickListener(OnClickListener manageClickListener) { - findViewById(R.id.settings_button).setOnClickListener(manageClickListener); + mSettingsIcon.setOnClickListener(manageClickListener); } /** - * Updates the ActivityView's obscured touchable region. This calls onLocationChanged, which - * results in a call to {@link BubbleStackView#subtractObscuredTouchableRegion}. This is useful - * if a view has been added or removed from on top of the ActivityView, such as the manage menu. + * Updates the obscured touchable region for the task surface. This calls onLocationChanged, + * which results in a call to {@link BubbleStackView#subtractObscuredTouchableRegion}. This is + * useful if a view has been added or removed from on top of the ActivityView, such as the + * manage menu. */ void updateObscuredTouchableRegion() { - if (mActivityView != null) { - mActivityView.onLocationChanged(); + if (mTaskView != null) { + mTaskView.onLocationChanged(); } } @@ -442,12 +376,12 @@ public class BubbleExpandedView extends LinearLayout { android.R.attr.dialogCornerRadius, android.R.attr.colorBackgroundFloating}); mCornerRadius = ta.getDimensionPixelSize(0, 0); - mActivityViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE)); + mExpandedViewContainer.setBackgroundColor(ta.getColor(1, Color.WHITE)); ta.recycle(); - if (mActivityView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows( + if (mTaskView != null && ScreenDecorationsUtils.supportsRoundedCornersOnWindows( mContext.getResources())) { - mActivityView.setCornerRadius(mCornerRadius); + mTaskView.setCornerRadius(mCornerRadius); } final int mode = @@ -466,11 +400,8 @@ public class BubbleExpandedView extends LinearLayout { @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - mKeyboardVisible = false; + mImeVisible = false; mNeedsNewHeight = false; - if (mActivityView != null) { - setImeWindowToDisplay(0, 0); - } if (DEBUG_BUBBLE_EXPANDED_VIEW) { Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey()); } @@ -492,84 +423,23 @@ public class BubbleExpandedView extends LinearLayout { final float alpha = visibility ? 1f : 0f; mPointerView.setAlpha(alpha); - - if (mActivityView != null && alpha != mActivityView.getAlpha()) { - mActivityView.setAlpha(alpha); - mActivityView.bringToFront(); + if (mTaskView == null) { + return; + } + if (alpha != mTaskView.getAlpha()) { + mTaskView.setAlpha(alpha); } } - @Nullable ActivityView getActivityView() { - return mActivityView; + @Nullable + View getTaskView() { + return mTaskView; } int getTaskId() { return mTaskId; } - /** - * Called by {@link BubbleStackView} when the insets for the expanded state should be updated. - * This should be done post-move and post-animation. - */ - void updateInsets(WindowInsets insets) { - if (usingActivityView()) { - int[] screenLoc = mActivityView.getLocationOnScreen(); - final int activityViewBottom = screenLoc[1] + mActivityView.getHeight(); - final int keyboardTop = mDisplaySize.y - Math.max(insets.getSystemWindowInsetBottom(), - insets.getDisplayCutout() != null - ? insets.getDisplayCutout().getSafeInsetBottom() - : 0); - setImeWindowToDisplay(getWidth(), Math.max(activityViewBottom - keyboardTop, 0)); - } - } - - private void setImeWindowToDisplay(int w, int h) { - if (getVirtualDisplayId() == INVALID_DISPLAY) { - return; - } - if (h == 0 || w == 0) { - if (mImeShowing) { - mVirtualImeView.setVisibility(GONE); - mImeShowing = false; - } - return; - } - final Context virtualDisplayContext = mContext.createDisplayContext( - getVirtualDisplay().getDisplay()); - - if (mVirtualDisplayWindowManager == null) { - mVirtualDisplayWindowManager = - (WindowManager) virtualDisplayContext.getSystemService(Context.WINDOW_SERVICE); - } - if (mVirtualImeView == null) { - mVirtualImeView = new View(virtualDisplayContext); - mVirtualImeView.setVisibility(VISIBLE); - mVirtualDisplayWindowManager.addView(mVirtualImeView, - getVirtualImeViewAttrs(w, h)); - } else { - mVirtualDisplayWindowManager.updateViewLayout(mVirtualImeView, - getVirtualImeViewAttrs(w, h)); - mVirtualImeView.setVisibility(VISIBLE); - } - - mImeShowing = true; - } - - private WindowManager.LayoutParams getVirtualImeViewAttrs(int w, int h) { - // To use TYPE_NAVIGATION_BAR_PANEL instead of TYPE_IME_BAR to bypass the IME window type - // token check when adding the window. - final WindowManager.LayoutParams attrs = - new WindowManager.LayoutParams(w, h, TYPE_NAVIGATION_BAR_PANEL, - FLAG_LAYOUT_NO_LIMITS | FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, - TRANSPARENT); - attrs.gravity = Gravity.BOTTOM; - attrs.setTitle(WINDOW_TITLE); - attrs.token = new Binder(); - attrs.providesInsetsTypes = new int[]{ITYPE_IME}; - attrs.alpha = 0.0f; - return attrs; - } - void setStackView(BubbleStackView stackView) { mStackView = stackView; } @@ -581,7 +451,7 @@ public class BubbleExpandedView extends LinearLayout { Bundle extras = new Bundle(); extras.putBinder(EXTRA_BUBBLE_CONTROLLER, ObjectWrapper.wrap(mBubbles)); target.putExtras(extras); - mPendingIntent = PendingIntent.getActivity(mContext, /* requestCode */ 0, + mPendingIntent = PendingIntent.getActivity(mContext, 0 /* requestCode */, target, PendingIntent.FLAG_UPDATE_CURRENT); mSettingsIcon.setVisibility(GONE); } @@ -619,7 +489,7 @@ public class BubbleExpandedView extends LinearLayout { mPendingIntent = mBubble.getBubbleIntent(); if (mPendingIntent != null || mBubble.hasMetadataShortcutId()) { setContentVisibility(false); - mActivityView.setVisibility(VISIBLE); + mTaskView.setVisibility(VISIBLE); } } applyThemeAttrs(); @@ -629,59 +499,42 @@ public class BubbleExpandedView extends LinearLayout { } } + /** + * Bubbles are backed by a pending intent or a shortcut, once the activity is + * started we never change it / restart it on notification updates -- unless the bubbles' + * backing data switches. + * + * This indicates if the new bubble is backed by a different data source than what was + * previously shown here (e.g. previously a pending intent & now a shortcut). + * + * @param newBubble the bubble this view is being updated with. + * @return true if the backing content has changed. + */ private boolean didBackingContentChange(Bubble newBubble) { boolean prevWasIntentBased = mBubble != null && mPendingIntent != null; boolean newIsIntentBased = newBubble.getBubbleIntent() != null; return prevWasIntentBased != newIsIntentBased; } - /** - * Lets activity view know it should be shown / populated with activity content. - */ - void populateExpandedView() { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "populateExpandedView: " - + "bubble=" + getBubbleKey()); - } - - if (usingActivityView()) { - mActivityView.setCallback(mStateCallback); - } else { - Log.e(TAG, "Cannot populate expanded view."); - } - } - - boolean performBackPressIfNeeded() { - if (!usingActivityView()) { - return false; - } - mActivityView.performBackPress(); - return true; - } - void updateHeight() { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()); - } - if (mExpandedViewContainerLocation == null) { return; } - if (usingActivityView()) { + if (mBubble != null || mIsOverflow) { float desiredHeight = mOverflowHeight; if (!mIsOverflow) { desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight); } float height = Math.min(desiredHeight, getMaxExpandedHeight()); - height = Math.max(height, mMinHeight); - ViewGroup.LayoutParams lp = mActivityView.getLayoutParams(); + height = Math.max(height, mIsOverflow ? mOverflowHeight : mMinHeight); + FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mTaskView.getLayoutParams(); mNeedsNewHeight = lp.height != height; - if (!mKeyboardVisible) { - // If the keyboard is visible... don't adjust the height because that will cause - // a configuration change and the keyboard will be lost. + if (!mImeVisible) { + // If the ime is visible... don't adjust the height because that will cause + // a configuration change and the ime will be lost. lp.height = (int) height; - mActivityView.setLayoutParams(lp); + mTaskView.setLayoutParams(lp); mNeedsNewHeight = false; } if (DEBUG_BUBBLE_EXPANDED_VIEW) { @@ -694,12 +547,15 @@ public class BubbleExpandedView extends LinearLayout { private int getMaxExpandedHeight() { mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize); + int expandedContainerY = mExpandedViewContainerLocation != null + ? mExpandedViewContainerLocation[1] + : 0; int bottomInset = getRootWindowInsets() != null ? getRootWindowInsets().getStableInsetBottom() : 0; return mDisplaySize.y - - mExpandedViewContainerLocation[1] + - expandedContainerY - getPaddingTop() - getPaddingBottom() - mSettingsIconHeight @@ -719,14 +575,12 @@ public class BubbleExpandedView extends LinearLayout { Log.d(TAG, "updateView: bubble=" + getBubbleKey()); } - mExpandedViewContainerLocation = containerLocationOnScreen; - - if (usingActivityView() - && mActivityView.getVisibility() == VISIBLE - && mActivityView.isAttachedToWindow()) { - mActivityView.onLocationChanged(); + if (mTaskView != null + && mTaskView.getVisibility() == VISIBLE + && mTaskView.isAttachedToWindow()) { updateHeight(); + mTaskView.onLocationChanged(); } } @@ -749,65 +603,19 @@ public class BubbleExpandedView extends LinearLayout { } /** - * Removes and releases an ActivityView if one was previously created for this bubble. + * Cleans up anything related to the task and TaskView. */ public void cleanUpExpandedState() { if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "cleanUpExpandedState: mActivityViewStatus=" + mActivityViewStatus - + ", bubble=" + getBubbleKey()); - } - if (mActivityView == null) { - return; - } - mActivityView.release(); - if (mTaskId != -1) { - try { - ActivityTaskManager.getService().removeTask(mTaskId); - } catch (RemoteException e) { - Log.w(TAG, "Failed to remove taskId " + mTaskId); - } - mTaskId = -1; - } - removeView(mActivityView); - - mActivityView = null; - } - - /** - * Called when the last task is removed from a {@link android.hardware.display.VirtualDisplay} - * which {@link ActivityView} uses. - */ - void notifyDisplayEmpty() { - if (DEBUG_BUBBLE_EXPANDED_VIEW) { - Log.d(TAG, "notifyDisplayEmpty: bubble=" - + getBubbleKey() - + " mActivityViewStatus=" + mActivityViewStatus); + Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId); } - if (mActivityViewStatus == ActivityViewStatus.ACTIVITY_STARTED) { - mActivityViewStatus = ActivityViewStatus.INITIALIZED; + if (mTaskView != null) { + mTaskView.release(); } - } - - private boolean usingActivityView() { - return (mPendingIntent != null || mBubble.hasMetadataShortcutId()) - && mActivityView != null; - } - - /** - * @return the display id of the virtual display. - */ - public int getVirtualDisplayId() { - if (usingActivityView()) { - return mActivityView.getVirtualDisplayId(); - } - return INVALID_DISPLAY; - } - - private VirtualDisplay getVirtualDisplay() { - if (usingActivityView()) { - return mActivityView.getVirtualDisplay(); + if (mTaskView != null) { + removeView(mTaskView); + mTaskView = null; } - return null; } /** @@ -817,7 +625,6 @@ public class BubbleExpandedView extends LinearLayout { @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { pw.print("BubbleExpandedView"); pw.print(" taskId: "); pw.println(mTaskId); - pw.print(" activityViewStatus: "); pw.println(mActivityViewStatus); pw.print(" stackView: "); pw.println(mStackView); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt index bf7c860132bf..102055de2bea 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.kt @@ -16,6 +16,7 @@ package com.android.systemui.bubbles +import android.app.ActivityTaskManager.INVALID_TASK_ID import android.content.Context import android.content.res.Configuration import android.graphics.Bitmap @@ -37,8 +38,8 @@ class BubbleOverflow( private val stack: BubbleStackView ) : BubbleViewProvider { - private lateinit var bitmap : Bitmap - private lateinit var dotPath : Path + private lateinit var bitmap: Bitmap + private lateinit var dotPath: Path private var bitmapSize = 0 private var iconBitmapSize = 0 @@ -167,8 +168,8 @@ class BubbleOverflow( return KEY } - override fun getDisplayId(): Int { - return expandedView.virtualDisplayId + override fun getTaskId(): Int { + return if (expandedView != null) expandedView.getTaskId() else INVALID_TASK_ID } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index c42236f6f1ed..e2674de3a723 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -27,7 +27,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.SuppressLint; -import android.app.ActivityView; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; @@ -299,7 +298,7 @@ public class BubbleStackView extends FrameLayout pw.println(" expandedViewAlpha: " + expandedView.getAlpha()); pw.println(" expandedViewTaskId: " + expandedView.getTaskId()); - final ActivityView av = expandedView.getActivityView(); + final View av = expandedView.getTaskView(); if (av != null) { pw.println(" activityViewVis: " + av.getVisibility()); @@ -327,8 +326,6 @@ public class BubbleStackView extends FrameLayout /** The view to desaturate/darken when magneted to the dismiss target. */ @Nullable private View mDesaturateAndDarkenTargetView; - private LayoutInflater mInflater; - private Rect mTempRect = new Rect(); private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect()); @@ -749,7 +746,6 @@ public class BubbleStackView extends FrameLayout super(context); mBubbleData = data; - mInflater = LayoutInflater.from(context); Resources res = getResources(); mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered); @@ -867,18 +863,9 @@ public class BubbleStackView extends FrameLayout setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> { onImeVisibilityChanged.accept(insets.getInsets(WindowInsets.Type.ime()).bottom > 0); - if (!mIsExpanded || mIsExpansionAnimating) { return view.onApplyWindowInsets(insets); } - mExpandedAnimationController.updateYPosition( - // Update the insets after we're done translating otherwise position - // calculation for them won't be correct. - () -> { - if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().updateInsets(insets); - } - }); return view.onApplyWindowInsets(insets); }); @@ -1256,7 +1243,15 @@ public class BubbleStackView extends FrameLayout mTempRect.setEmpty(); getTouchableRegion(mTempRect); - inoutInfo.touchableRegion.set(mTempRect); + if (mIsExpanded && mExpandedBubble != null + && mExpandedBubble.getExpandedView() != null + && mExpandedBubble.getExpandedView().getTaskView() != null) { + inoutInfo.touchableRegion.set(mTempRect); + mExpandedBubble.getExpandedView().getTaskView().getBoundsOnScreen(mTempRect); + inoutInfo.touchableRegion.op(mTempRect, Region.Op.DIFFERENCE); + } else { + inoutInfo.touchableRegion.set(mTempRect); + } } @Override @@ -1444,13 +1439,6 @@ public class BubbleStackView extends FrameLayout } /** - * The {@link BadgedImageView} that is expanded, null if one does not exist. - */ - View getExpandedBubbleView() { - return mExpandedBubble != null ? mExpandedBubble.getIconView() : null; - } - - /** * The {@link Bubble} that is expanded, null if one does not exist. */ @Nullable @@ -1565,7 +1553,7 @@ public class BubbleStackView extends FrameLayout return; } - if (bubbleToSelect.getKey() == BubbleOverflow.KEY) { + if (bubbleToSelect.getKey().equals(BubbleOverflow.KEY)) { mBubbleData.setShowingOverflow(true); } else { mBubbleData.setShowingOverflow(false); @@ -1671,14 +1659,6 @@ public class BubbleStackView extends FrameLayout notifyExpansionChanged(mExpandedBubble, mIsExpanded); } - void showExpandedViewContents(int displayId) { - if (mExpandedBubble != null - && mExpandedBubble.getExpandedView() != null - && mExpandedBubble.getExpandedView().getVirtualDisplayId() == displayId) { - mExpandedBubble.setContentVisibility(true); - } - } - /** * Asks the BubbleController to hide the IME from anywhere, whether it's focused on Bubbles or * not. @@ -1713,7 +1693,6 @@ public class BubbleStackView extends FrameLayout updateOverflowVisibility(); updatePointerPosition(); mExpandedAnimationController.expandFromStack(() -> { - afterExpandedViewAnimation(); if (mIsExpanded && mExpandedBubble.getExpandedView() != null) { maybeShowManageEdu(); } @@ -1776,11 +1755,10 @@ public class BubbleStackView extends FrameLayout mExpandedViewContainerMatrix); }) .withEndActions(() -> { + afterExpandedViewAnimation(); if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { mExpandedBubble.getExpandedView() - .setContentVisibility(true); - mExpandedBubble.getExpandedView() .setSurfaceZOrderedOnTop(false); } }) @@ -1924,7 +1902,6 @@ public class BubbleStackView extends FrameLayout }) .withEndActions(() -> { if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { - mExpandedBubble.getExpandedView().setContentVisibility(true); mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false); } @@ -1960,13 +1937,6 @@ public class BubbleStackView extends FrameLayout } } - /** Return the BubbleView at the given index from the bubble container. */ - public BadgedImageView getBubbleAt(int i) { - return getBubbleCount() > i - ? (BadgedImageView) mBubbleContainer.getChildAt(i) - : null; - } - /** Moves the bubbles out of the way if they're going to be over the keyboard. */ public void onImeVisibilityChanged(boolean visible, int height) { mStackAnimationController.setImeHeight(visible ? height + mImeOffset : 0); @@ -1988,6 +1958,9 @@ public class BubbleStackView extends FrameLayout FLYOUT_IME_ANIMATION_SPRING_CONFIG) .start(); } + } else if (mIsExpanded && mExpandedBubble != null + && mExpandedBubble.getExpandedView() != null) { + mExpandedBubble.getExpandedView().setImeVisible(visible); } } @@ -2395,7 +2368,6 @@ public class BubbleStackView extends FrameLayout final float targetY = mTempRect.bottom - mManageMenu.getHeight(); final float xOffsetForAnimation = (isLtr ? 1 : -1) * mManageMenu.getWidth() / 4f; - if (show) { mManageMenu.setScaleX(0.5f); mManageMenu.setScaleY(0.5f); @@ -2412,6 +2384,8 @@ public class BubbleStackView extends FrameLayout .withEndActions(() -> { View child = mManageMenu.getChildAt(0); child.requestAccessibilityFocus(); + // Update the AV's obscured touchable region for the new visibility state. + mExpandedBubble.getExpandedView().updateObscuredTouchableRegion(); }) .start(); @@ -2423,12 +2397,15 @@ public class BubbleStackView extends FrameLayout .spring(DynamicAnimation.SCALE_Y, 0.5f) .spring(DynamicAnimation.TRANSLATION_X, targetX - xOffsetForAnimation) .spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4f) - .withEndActions(() -> mManageMenu.setVisibility(View.INVISIBLE)) + .withEndActions(() -> { + mManageMenu.setVisibility(View.INVISIBLE); + if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) { + // Update the AV's obscured touchable region for the new state. + mExpandedBubble.getExpandedView().updateObscuredTouchableRegion(); + } + }) .start(); } - - // Update the AV's obscured touchable region for the new menu visibility state. - mExpandedBubble.getExpandedView().updateObscuredTouchableRegion(); } private void updateExpandedBubble() { @@ -2448,7 +2425,6 @@ public class BubbleStackView extends FrameLayout mExpandedViewContainer.setAlpha(0f); mExpandedViewContainer.addView(bev); bev.setManageClickListener((view) -> showManageMenu(!mShowingManage)); - bev.populateExpandedView(); if (!mIsExpansionAnimating) { mSurfaceSynchronizer.syncSurfaceAndRun(() -> { @@ -2505,7 +2481,7 @@ public class BubbleStackView extends FrameLayout mAnimatingOutSurfaceContainer.setTranslationY(0); final int[] activityViewLocation = - mExpandedBubble.getExpandedView().getActivityViewLocationOnScreen(); + mExpandedBubble.getExpandedView().getTaskViewLocationOnScreen(); final int[] surfaceViewLocation = mAnimatingOutSurfaceView.getLocationOnScreen(); // Translate the surface to overlap the real ActivityView. @@ -2681,17 +2657,6 @@ public class BubbleStackView extends FrameLayout getNormalizedYPosition()); } - /** - * Called when a back gesture should be directed to the Bubbles stack. When expanded, - * a back key down/up event pair is forwarded to the bubble Activity. - */ - boolean performBackPressIfNeeded() { - if (!isExpanded() || mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) { - return false; - } - return mExpandedBubble.getExpandedView().performBackPressIfNeeded(); - } - /** For debugging only */ List<Bubble> getBubblesOnScreen() { List<Bubble> bubbles = new ArrayList<>(); diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java deleted file mode 100644 index 06205c5c1c41..000000000000 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright (C) 2020 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.systemui.bubbles; - -import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; -import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.app.ActivityOptions; -import android.app.PendingIntent; -import android.window.TaskEmbedder; -import android.window.TaskOrganizerTaskEmbedder; -import android.content.Context; -import android.content.Intent; -import android.content.pm.ShortcutInfo; -import android.graphics.Matrix; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.Region; -import android.view.IWindow; -import android.view.SurfaceControl; -import android.view.SurfaceHolder; -import android.view.SurfaceView; - -import dalvik.system.CloseGuard; - - -public class BubbleTaskView extends SurfaceView implements SurfaceHolder.Callback, - TaskEmbedder.Host { - private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleTaskView" : TAG_BUBBLES; - - private final CloseGuard mGuard = CloseGuard.get(); - private boolean mOpened; // Protected by mGuard. - - private TaskEmbedder mTaskEmbedder; - private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction(); - private final Rect mTmpRect = new Rect(); - - public BubbleTaskView(Context context) { - super(context); - - mTaskEmbedder = new TaskOrganizerTaskEmbedder(context, this); - setUseAlpha(); - getHolder().addCallback(this); - - mOpened = true; - mGuard.open("release"); - } - - public void setCallback(TaskEmbedder.Listener callback) { - if (callback == null) { - mTaskEmbedder.setListener(null); - return; - } - mTaskEmbedder.setListener(callback); - } - - public void startShortcutActivity(@NonNull ShortcutInfo shortcut, - @NonNull ActivityOptions options, @Nullable Rect sourceBounds) { - mTaskEmbedder.startShortcutActivity(shortcut, options, sourceBounds); - } - - public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, - @NonNull ActivityOptions options) { - mTaskEmbedder.startActivity(pendingIntent, fillInIntent, options); - } - - public void onLocationChanged() { - mTaskEmbedder.notifyBoundsChanged(); - } - - @Override - public Rect getScreenBounds() { - getBoundsOnScreen(mTmpRect); - return mTmpRect; - } - - @Override - public void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor) { - setResizeBackgroundColor(bgColor); - } - - @Override - public Region getTapExcludeRegion() { - // Not used - return null; - } - - @Override - public Matrix getScreenToTaskMatrix() { - // Not used - return null; - } - - @Override - public IWindow getWindow() { - // Not used - return null; - } - - @Override - public Point getPositionInWindow() { - // Not used - return null; - } - - @Override - public boolean canReceivePointerEvents() { - // Not used - return false; - } - - public void release() { - if (!mTaskEmbedder.isInitialized()) { - throw new IllegalStateException( - "Trying to release container that is not initialized."); - } - performRelease(); - } - - @Override - protected void finalize() throws Throwable { - try { - if (mGuard != null) { - mGuard.warnIfOpen(); - performRelease(); - } - } finally { - super.finalize(); - } - } - - private void performRelease() { - if (!mOpened) { - return; - } - getHolder().removeCallback(this); - mTaskEmbedder.release(); - mTaskEmbedder.setListener(null); - - mGuard.close(); - mOpened = false; - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - if (!mTaskEmbedder.isInitialized()) { - mTaskEmbedder.initialize(getSurfaceControl()); - } else { - mTmpTransaction.reparent(mTaskEmbedder.getSurfaceControl(), - getSurfaceControl()).apply(); - } - mTaskEmbedder.start(); - } - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - mTaskEmbedder.resizeTask(width, height); - mTaskEmbedder.notifyBoundsChanged(); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - mTaskEmbedder.stop(); - } -} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java index 916ad18b2812..5cc24ce5a775 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java @@ -48,5 +48,5 @@ interface BubbleViewProvider { boolean showDot(); - int getDisplayId(); + int getTaskId(); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java index 34828b384939..39c750de28ac 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubbles.java @@ -17,8 +17,6 @@ package com.android.systemui.bubbles; import android.annotation.NonNull; -import android.content.Context; -import android.view.Display; import androidx.annotation.MainThread; @@ -57,12 +55,6 @@ public interface Bubbles { */ ScrimView getScrimForBubble(); - /** - * @return the display id of the expanded view, if the stack is expanded and not occluded by the - * status bar, otherwise returns {@link Display#INVALID_DISPLAY}. - */ - int getExpandedDisplayId(Context context); - /** @return Bubbles for updating overflow. */ List<Bubble> getOverflowBubbles(); @@ -77,13 +69,6 @@ public interface Bubbles { */ void expandStackAndSelectBubble(NotificationEntry entry); - - /** - * Directs a back gesture at the bubble stack. When opened, the current expanded bubble - * is forwarded a back key down/up pair. - */ - void performBackPressIfNeeded(); - /** Promote the provided bubbles when overflow view. */ void promoteBubbleFromOverflow(Bubble bubble); @@ -142,4 +127,7 @@ public interface Bubbles { /** Set a listener to be notified of when overflow view update. */ void setOverflowListener(BubbleData.Listener listener); + + /** The task listener for events in bubble tasks. **/ + MultiWindowTaskListener getTaskManager(); } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java b/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java new file mode 100644 index 000000000000..dff8becccb86 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/MultiWindowTaskListener.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2020 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.systemui.bubbles; + +import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW; + +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityTaskManager; +import android.os.Handler; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Log; +import android.view.SurfaceControl; +import android.window.TaskOrganizer; +import android.window.WindowContainerToken; + +import com.android.wm.shell.ShellTaskOrganizer; + +/** + * Manages tasks that are displayed in multi-window (e.g. bubbles). These are displayed in a + * {@link TaskView}. + * + * This class listens on {@link TaskOrganizer} callbacks for events. Once visible, these tasks will + * intercept back press events. + * + * @see android.app.WindowConfiguration#WINDOWING_MODE_MULTI_WINDOW + * @see TaskView + */ +// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration. +public class MultiWindowTaskListener implements ShellTaskOrganizer.TaskListener { + private static final String TAG = MultiWindowTaskListener.class.getSimpleName(); + + private static final boolean DEBUG = false; + + //TODO(b/170153209): Have shell listener allow per task registration and remove this. + public interface Listener { + void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash); + void onTaskVanished(RunningTaskInfo taskInfo); + void onTaskInfoChanged(RunningTaskInfo taskInfo); + void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo); + } + + private static class TaskData { + final RunningTaskInfo taskInfo; + final Listener listener; + + TaskData(RunningTaskInfo info, Listener l) { + taskInfo = info; + listener = l; + } + } + + private final Handler mHandler; + private final ShellTaskOrganizer mTaskOrganizer; + private final ArrayMap<WindowContainerToken, TaskData> mTasks = new ArrayMap<>(); + + private MultiWindowTaskListener.Listener mPendingListener; + + /** + * Create a listener for tasks in multi-window mode. + */ + public MultiWindowTaskListener(Handler handler, ShellTaskOrganizer organizer) { + mHandler = handler; + mTaskOrganizer = organizer; + mTaskOrganizer.addListener(this, TASK_LISTENER_TYPE_MULTI_WINDOW); + } + + /** + * @return the task organizer that is listened to. + */ + public TaskOrganizer getTaskOrganizer() { + return mTaskOrganizer; + } + + // TODO(b/129067201): track launches for bubbles + // Once we have key in ActivityOptions, match listeners via that key + public void setPendingListener(Listener listener) { + mPendingListener = listener; + } + + /** + * Removes a task listener previously registered when starting a new activity. + */ + public void removeListener(Listener listener) { + if (DEBUG) { + Log.d(TAG, "removeListener: listener=" + listener); + } + if (mPendingListener == listener) { + mPendingListener = null; + } + for (int i = 0; i < mTasks.size(); i++) { + if (mTasks.valueAt(i).listener == listener) { + mTasks.removeAt(i); + } + } + } + + @Override + public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) { + if (DEBUG) { + Log.d(TAG, "onTaskAppeared: taskInfo=" + taskInfo + + " mPendingListener=" + mPendingListener); + } + if (mPendingListener == null) { + // If there is no pending listener, then we are either receiving this task as a part of + // registering the task org again (ie. after SysUI dies) or the previously started + // task is no longer needed (ie. bubble is closed soon after), for now, just finish the + // associated task + try { + ActivityTaskManager.getService().removeTask(taskInfo.taskId); + } catch (RemoteException e) { + Log.w(TAG, "Failed to remove taskId " + taskInfo.taskId); + } + return; + } + + mTaskOrganizer.setInterceptBackPressedOnTaskRoot(taskInfo.token, true); + + final TaskData data = new TaskData(taskInfo, mPendingListener); + mTasks.put(taskInfo.token, data); + mHandler.post(() -> data.listener.onTaskAppeared(taskInfo, leash)); + mPendingListener = null; + } + + @Override + public void onTaskVanished(RunningTaskInfo taskInfo) { + final TaskData data = mTasks.remove(taskInfo.token); + if (data == null) { + return; + } + + if (DEBUG) { + Log.d(TAG, "onTaskVanished: taskInfo=" + taskInfo + " listener=" + data.listener); + } + mHandler.post(() -> data.listener.onTaskVanished(taskInfo)); + } + + @Override + public void onTaskInfoChanged(RunningTaskInfo taskInfo) { + final TaskData data = mTasks.get(taskInfo.token); + if (data == null) { + return; + } + + if (DEBUG) { + Log.d(TAG, "onTaskInfoChanged: taskInfo=" + taskInfo + " listener=" + data.listener); + } + mHandler.post(() -> data.listener.onTaskInfoChanged(taskInfo)); + } + + @Override + public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { + final TaskData data = mTasks.get(taskInfo.token); + if (data == null) { + return; + } + + if (DEBUG) { + Log.d(TAG, "onTaskInfoChanged: taskInfo=" + taskInfo + " listener=" + data.listener); + } + mHandler.post(() -> data.listener.onBackPressedOnTaskRoot(taskInfo)); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java b/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java new file mode 100644 index 000000000000..524fa42af7d5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java @@ -0,0 +1,308 @@ +/* + * Copyright (C) 2020 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.systemui.bubbles; + +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.LauncherApps; +import android.content.pm.ShortcutInfo; +import android.graphics.Rect; +import android.view.SurfaceControl; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.window.WindowContainerToken; +import android.window.WindowContainerTransaction; + +import dalvik.system.CloseGuard; + +/** + * View that can display a task. + */ +// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration. +public class TaskView extends SurfaceView implements SurfaceHolder.Callback, + MultiWindowTaskListener.Listener { + + public interface Listener { + /** Called when the container is ready for launching activities. */ + default void onInitialized() {} + + /** Called when the container can no longer launch activities. */ + default void onReleased() {} + + /** Called when a task is created inside the container. */ + default void onTaskCreated(int taskId, ComponentName name) {} + + /** Called when a task visibility changes. */ + default void onTaskVisibilityChanged(int taskId, boolean visible) {} + + /** Called when a task is about to be removed from the stack inside the container. */ + default void onTaskRemovalStarted(int taskId) {} + + /** Called when a task is created inside the container. */ + default void onBackPressedOnTaskRoot(int taskId) {} + } + + private final CloseGuard mGuard = CloseGuard.get(); + + private final MultiWindowTaskListener mMultiWindowTaskListener; + + private ActivityManager.RunningTaskInfo mTaskInfo; + private WindowContainerToken mTaskToken; + private SurfaceControl mTaskLeash; + private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction(); + private boolean mSurfaceCreated; + private boolean mIsInitialized; + private Listener mListener; + + private final Rect mTmpRect = new Rect(); + private final Rect mTmpRootRect = new Rect(); + + public TaskView(Context context, MultiWindowTaskListener taskListener) { + super(context, null, 0, 0, true /* disableBackgroundLayer */); + + mMultiWindowTaskListener = taskListener; + setUseAlpha(); + getHolder().addCallback(this); + mGuard.open("release"); + } + + /** + * Only one listener may be set on the view, throws an exception otherwise. + */ + public void setListener(Listener listener) { + if (mListener != null) { + throw new IllegalStateException( + "Trying to set a listener when one has already been set"); + } + mListener = listener; + } + + /** + * Launch an activity represented by {@link ShortcutInfo}. + * <p>The owner of this container must be allowed to access the shortcut information, + * as defined in {@link LauncherApps#hasShortcutHostPermission()} to use this method. + * + * @param shortcut the shortcut used to launch the activity. + * @param options options for the activity. + * @param sourceBounds the rect containing the source bounds of the clicked icon to open + * this shortcut. + */ + public void startShortcutActivity(@NonNull ShortcutInfo shortcut, + @NonNull ActivityOptions options, @Nullable Rect sourceBounds) { + mMultiWindowTaskListener.setPendingListener(this); + prepareActivityOptions(options); + LauncherApps service = mContext.getSystemService(LauncherApps.class); + try { + service.startShortcut(shortcut, sourceBounds, options.toBundle()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Launch a new activity. + * + * @param pendingIntent Intent used to launch an activity. + * @param fillInIntent Additional Intent data, see {@link Intent#fillIn Intent.fillIn()} + * @param options options for the activity. + */ + public void startActivity(@NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, + @NonNull ActivityOptions options) { + mMultiWindowTaskListener.setPendingListener(this); + prepareActivityOptions(options); + try { + pendingIntent.send(mContext, 0 /* code */, fillInIntent, + null /* onFinished */, null /* handler */, null /* requiredPermission */, + options.toBundle()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private void prepareActivityOptions(ActivityOptions options) { + options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW); + options.setTaskAlwaysOnTop(true); + } + + /** + * Call when view position or size has changed. Do not call when animating. + */ + public void onLocationChanged() { + if (mTaskToken == null) { + return; + } + // Update based on the screen bounds + getBoundsOnScreen(mTmpRect); + getRootView().getBoundsOnScreen(mTmpRootRect); + if (!mTmpRootRect.contains(mTmpRect)) { + mTmpRect.offsetTo(0, 0); + } + + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setBounds(mTaskToken, mTmpRect); + // TODO(b/151449487): Enable synchronization + mMultiWindowTaskListener.getTaskOrganizer().applyTransaction(wct); + } + + /** + * Release this container if it is initialized. + */ + public void release() { + performRelease(); + } + + @Override + protected void finalize() throws Throwable { + try { + if (mGuard != null) { + mGuard.warnIfOpen(); + performRelease(); + } + } finally { + super.finalize(); + } + } + + private void performRelease() { + getHolder().removeCallback(this); + mMultiWindowTaskListener.removeListener(this); + resetTaskInfo(); + mGuard.close(); + if (mListener != null && mIsInitialized) { + mListener.onReleased(); + mIsInitialized = false; + } + } + + private void resetTaskInfo() { + mTaskInfo = null; + mTaskToken = null; + mTaskLeash = null; + } + + private void updateTaskVisibility() { + WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setHidden(mTaskToken, !mSurfaceCreated /* hidden */); + mMultiWindowTaskListener.getTaskOrganizer().applyTransaction(wct); + // TODO(b/151449487): Only call callback once we enable synchronization + if (mListener != null) { + mListener.onTaskVisibilityChanged(mTaskInfo.taskId, mSurfaceCreated); + } + } + + @Override + public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, + SurfaceControl leash) { + mTaskInfo = taskInfo; + mTaskToken = taskInfo.token; + mTaskLeash = leash; + + if (mSurfaceCreated) { + // Surface is ready, so just reparent the task to this surface control + mTransaction.reparent(mTaskLeash, getSurfaceControl()) + .show(mTaskLeash) + .apply(); + } else { + // The surface has already been destroyed before the task has appeared, so go ahead and + // hide the task entirely + updateTaskVisibility(); + } + + // TODO: Synchronize show with the resize + onLocationChanged(); + setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor()); + + if (mListener != null) { + mListener.onTaskCreated(taskInfo.taskId, taskInfo.baseActivity); + } + } + + @Override + public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) { + if (mTaskToken != null && mTaskToken.equals(taskInfo.token)) { + if (mListener != null) { + mListener.onTaskRemovalStarted(taskInfo.taskId); + } + + // Unparent the task when this surface is destroyed + mTransaction.reparent(mTaskLeash, null).apply(); + resetTaskInfo(); + } + } + + @Override + public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) { + mTaskInfo.taskDescription = taskInfo.taskDescription; + setResizeBackgroundColor(taskInfo.taskDescription.getBackgroundColor()); + } + + @Override + public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) { + if (mTaskToken != null && mTaskToken.equals(taskInfo.token)) { + if (mListener != null) { + mListener.onBackPressedOnTaskRoot(taskInfo.taskId); + } + } + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + mSurfaceCreated = true; + if (mListener != null && !mIsInitialized) { + mIsInitialized = true; + mListener.onInitialized(); + } + if (mTaskToken == null) { + // Nothing to update, task is not yet available + return; + } + // Reparent the task when this surface is created + mTransaction.reparent(mTaskLeash, getSurfaceControl()) + .show(mTaskLeash) + .apply(); + updateTaskVisibility(); + } + + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + if (mTaskToken == null) { + return; + } + onLocationChanged(); + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + mSurfaceCreated = false; + if (mTaskToken == null) { + // Nothing to update, task is not yet available + return; + } + + // Unparent the task when this surface is destroyed + mTransaction.reparent(mTaskLeash, null).apply(); + updateTaskVisibility(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java index 88f3ce1c933b..6b5f237ac76f 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/dagger/BubbleModule.java @@ -19,6 +19,7 @@ package com.android.systemui.bubbles.dagger; import android.app.INotificationManager; import android.content.Context; import android.content.pm.LauncherApps; +import android.os.Handler; import android.view.WindowManager; import com.android.internal.logging.UiEventLogger; @@ -26,6 +27,7 @@ import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.bubbles.BubbleController; import com.android.systemui.bubbles.Bubbles; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dump.DumpManager; import com.android.systemui.model.SysUiState; import com.android.systemui.plugins.statusbar.StatusBarStateController; @@ -39,6 +41,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.FloatingContentCoordinator; @@ -74,7 +77,9 @@ public interface BubbleModule { WindowManager windowManager, WindowManagerShellWrapper windowManagerShellWrapper, LauncherApps launcherApps, - UiEventLogger uiEventLogger) { + UiEventLogger uiEventLogger, + @Main Handler mainHandler, + ShellTaskOrganizer organizer) { return BubbleController.create( context, notificationShadeWindowController, @@ -97,6 +102,8 @@ public interface BubbleModule { windowManager, windowManagerShellWrapper, launcherApps, - uiEventLogger); + uiEventLogger, + mainHandler, + organizer); } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java index af851a7b768d..4efe4d85156b 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java @@ -58,7 +58,6 @@ import com.android.internal.logging.UiEventLoggerImpl; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.systemui.Dependency; import com.android.systemui.R; -import com.android.systemui.bubbles.Bubbles; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.shared.system.QuickStepContract; @@ -426,12 +425,6 @@ public class KeyButtonView extends ImageView implements ButtonInterface { if (getDisplay() != null) { displayId = getDisplay().getDisplayId(); } - // Bubbles will give us a valid display id if it should get the back event - Bubbles Bubbles = Dependency.get(Bubbles.class); - int bubbleDisplayId = Bubbles.getExpandedDisplayId(mContext); - if (mCode == KeyEvent.KEYCODE_BACK && bubbleDisplayId != INVALID_DISPLAY) { - displayId = bubbleDisplayId; - } if (displayId != INVALID_DISPLAY) { ev.setDisplayId(displayId); } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index beca7c370f2f..18cc746666d8 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -15,8 +15,6 @@ */ package com.android.systemui.navigationbar.gestural; -import static android.view.Display.INVALID_DISPLAY; - import android.app.ActivityManager; import android.content.ComponentName; import android.content.Context; @@ -57,7 +55,6 @@ import com.android.systemui.Dependency; import com.android.systemui.R; import com.android.systemui.SystemUIFactory; import com.android.systemui.broadcast.BroadcastDispatcher; -import com.android.systemui.bubbles.Bubbles; import com.android.systemui.model.SysUiState; import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; @@ -734,13 +731,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker implements Displa KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY, InputDevice.SOURCE_KEYBOARD); - // Bubbles will give us a valid display id if it should get the back event - final int bubbleDisplayId = Dependency.get(Bubbles.class).getExpandedDisplayId(mContext); - if (bubbleDisplayId != INVALID_DISPLAY) { - ev.setDisplayId(bubbleDisplayId); - } else { - ev.setDisplayId(mContext.getDisplay().getDisplayId()); - } + ev.setDisplayId(mContext.getDisplay().getDisplayId()); InputManager.getInstance().injectInputEvent(ev, InputManager.INJECT_INPUT_EVENT_MODE_ASYNC); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index e7c29b6b54b0..456e99ceecb2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -3539,8 +3539,6 @@ public class StatusBar extends SystemUI implements DemoMode, if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) { if (mNotificationPanelViewController.canPanelBeCollapsed()) { mShadeController.animateCollapsePanels(); - } else if (mBubblesOptional.isPresent()) { - mBubblesOptional.get().performBackPressIfNeeded(); } return true; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java index f678c5501fcb..b082d17e58ca 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java @@ -91,6 +91,7 @@ import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.FloatingContentCoordinator; @@ -276,7 +277,9 @@ public class BubbleControllerTest extends SysuiTestCase { mWindowManager, mWindowManagerShellWrapper, mLauncherApps, - mBubbleLogger); + mBubbleLogger, + mock(Handler.class), + mock(ShellTaskOrganizer.class)); mBubbleController.setExpandListener(mBubbleExpandListener); // Get a reference to the BubbleController's entry listener diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/MultiWindowTaskListenerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/MultiWindowTaskListenerTest.java new file mode 100644 index 000000000000..7c1b41443e26 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/MultiWindowTaskListenerTest.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2020 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.systemui.bubbles; + +import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; + +import android.app.ActivityManager; +import android.os.Handler; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.SurfaceControl; +import android.window.WindowContainerToken; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.wm.shell.ShellTaskOrganizer; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration. +public class MultiWindowTaskListenerTest extends SysuiTestCase { + + @Mock + ShellTaskOrganizer mOrganizer; + @Mock + MultiWindowTaskListener.Listener mPendingListener; + @Mock + SurfaceControl mLeash; + @Mock + ActivityManager.RunningTaskInfo mTaskInfo; + @Mock + WindowContainerToken mToken; + + Handler mHandler; + MultiWindowTaskListener mTaskListener; + TestableLooper mTestableLooper; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mTestableLooper = TestableLooper.get(this); + mHandler = new Handler(mTestableLooper.getLooper()); + + mTaskInfo = new ActivityManager.RunningTaskInfo(); + mTaskInfo.token = mToken; + + mTaskListener = new MultiWindowTaskListener(mHandler, mOrganizer); + } + + private void addTaskAndVerify() { + mTaskListener.setPendingListener(mPendingListener); + mTaskListener.onTaskAppeared(mTaskInfo, mLeash); + mTestableLooper.processAllMessages(); + verify(mPendingListener).onTaskAppeared(eq(mTaskInfo), eq(mLeash)); + } + + @Test + public void testListenForMultiWindowMode() { + mTaskListener = new MultiWindowTaskListener(mHandler, mOrganizer); + verify(mOrganizer).addListener(eq(mTaskListener), eq(TASK_LISTENER_TYPE_MULTI_WINDOW)); + } + + @Test + public void testRemovePendingListener() { + addTaskAndVerify(); + reset(mPendingListener); + + mTaskListener.removeListener(mPendingListener); + + // If it was removed, our pendingListener shouldn't get triggered: + mTaskListener.onTaskAppeared(mTaskInfo, mLeash); + mTaskListener.onTaskInfoChanged(mTaskInfo); + mTaskListener.onBackPressedOnTaskRoot(mTaskInfo); + mTaskListener.onTaskVanished(mTaskInfo); + + mTestableLooper.processAllMessages(); + verify(mPendingListener, never()).onTaskAppeared(any(), any()); + verify(mPendingListener, never()).onTaskInfoChanged(any()); + verify(mPendingListener, never()).onBackPressedOnTaskRoot(any()); + verify(mPendingListener, never()).onTaskVanished(any()); + } + + @Test + public void testOnTaskAppeared() { + addTaskAndVerify(); + verify(mOrganizer).setInterceptBackPressedOnTaskRoot(eq(mToken), eq(true)); + } + + @Test + public void testOnTaskAppeared_nullListener() { + mTaskListener.onTaskAppeared(mTaskInfo, mLeash); + mTestableLooper.processAllMessages(); + + verify(mOrganizer, never()).setInterceptBackPressedOnTaskRoot(any(), anyBoolean()); + verify(mPendingListener, never()).onTaskAppeared(any(), any()); + } + + @Test + public void testOnTaskVanished() { + addTaskAndVerify(); + mTaskListener.onTaskVanished(mTaskInfo); + mTestableLooper.processAllMessages(); + + verify(mPendingListener).onTaskVanished(eq(mTaskInfo)); + } + + @Test + public void testOnTaskVanished_neverAdded() { + mTaskListener.onTaskVanished(mTaskInfo); + mTestableLooper.processAllMessages(); + + verify(mPendingListener, never()).onTaskVanished(any()); + } + + @Test + public void testOnTaskInfoChanged() { + addTaskAndVerify(); + mTaskListener.onTaskInfoChanged(mTaskInfo); + mTestableLooper.processAllMessages(); + + verify(mPendingListener).onTaskInfoChanged(eq(mTaskInfo)); + } + + @Test + public void testOnTaskInfoChanged_neverAdded() { + mTaskListener.onTaskInfoChanged(mTaskInfo); + mTestableLooper.processAllMessages(); + + verify(mPendingListener, never()).onTaskInfoChanged(any()); + } + + @Test + public void testOnBackPressedOnTaskRoot() { + addTaskAndVerify(); + mTaskListener.onBackPressedOnTaskRoot(mTaskInfo); + mTestableLooper.processAllMessages(); + + verify(mPendingListener).onBackPressedOnTaskRoot(eq(mTaskInfo)); + } + + @Test + public void testOnBackPressedOnTaskRoot_neverAdded() { + mTaskListener.onBackPressedOnTaskRoot(mTaskInfo); + mTestableLooper.processAllMessages(); + + verify(mPendingListener, never()).onBackPressedOnTaskRoot(any()); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java index 9728383fd38b..cbacd5393ccf 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/NewNotifPipelineBubbleControllerTest.java @@ -91,6 +91,7 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.ZenModeController; import com.android.systemui.util.InjectionInflationController; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.FloatingContentCoordinator; @@ -278,7 +279,9 @@ public class NewNotifPipelineBubbleControllerTest extends SysuiTestCase { mWindowManager, mWindowManagerShellWrapper, mLauncherApps, - mBubbleLogger); + mBubbleLogger, + mock(Handler.class), + mock(ShellTaskOrganizer.class)); mBubbleController.addNotifCallback(mNotifCallback); mBubbleController.setExpandListener(mBubbleExpandListener); diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TaskViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TaskViewTest.java new file mode 100644 index 000000000000..6f3968dc5819 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TaskViewTest.java @@ -0,0 +1,212 @@ +/* + * Copyright (C) 2020 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.systemui.bubbles; + +import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.PendingIntent; +import android.content.Context; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.SurfaceControl; +import android.view.SurfaceHolder; +import android.view.SurfaceSession; +import android.window.WindowContainerToken; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.wm.shell.ShellTaskOrganizer; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +// TODO: Place in com.android.wm.shell vs. com.android.wm.shell.bubbles on shell migration. +public class TaskViewTest extends SysuiTestCase { + + @Mock + TaskView.Listener mViewListener; + @Mock + ActivityManager.RunningTaskInfo mTaskInfo; + @Mock + WindowContainerToken mToken; + @Mock + ShellTaskOrganizer mOrganizer; + @Mock + MultiWindowTaskListener mTaskListener; + + SurfaceSession mSession; + SurfaceControl mLeash; + + Context mContext; + TaskView mTaskView; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mLeash = new SurfaceControl.Builder(mSession) + .setName("test") + .build(); + + mContext = getContext(); + + when(mTaskListener.getTaskOrganizer()).thenReturn(mOrganizer); + mTaskInfo = new ActivityManager.RunningTaskInfo(); + mTaskInfo.token = mToken; + mTaskInfo.taskId = 314; + mTaskInfo.taskDescription = mock(ActivityManager.TaskDescription.class); + + mTaskView = new TaskView(mContext, mTaskListener); + mTaskView.setListener(mViewListener); + } + + @After + public void tearDown() { + if (mTaskView != null) { + mTaskView.release(); + } + } + + @Test + public void testSetPendingListener_throwsException() { + TaskView taskView = new TaskView(mContext, mTaskListener); + taskView.setListener(mViewListener); + try { + taskView.setListener(mViewListener); + } catch (IllegalStateException e) { + // pass + return; + } + fail("Expected IllegalStateException"); + } + + @Test + public void testStartActivity() { + ActivityOptions options = ActivityOptions.makeBasic(); + mTaskView.startActivity(mock(PendingIntent.class), null, options); + + verify(mTaskListener).setPendingListener(eq(mTaskView)); + assertThat(options.getLaunchWindowingMode()).isEqualTo(WINDOWING_MODE_MULTI_WINDOW); + assertThat(options.getTaskAlwaysOnTop()).isTrue(); + } + + @Test + public void testOnTaskAppeared_noSurface() { + mTaskView.onTaskAppeared(mTaskInfo, mLeash); + + verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any()); + verify(mViewListener, never()).onInitialized(); + // If there's no surface the task should be made invisible + verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false)); + } + + @Test + public void testOnTaskAppeared_withSurface() { + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + mTaskView.onTaskAppeared(mTaskInfo, mLeash); + + verify(mViewListener).onTaskCreated(eq(mTaskInfo.taskId), any()); + verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean()); + } + + @Test + public void testSurfaceCreated_noTask() { + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + + verify(mViewListener).onInitialized(); + // No task, no visibility change + verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean()); + } + + @Test + public void testSurfaceCreated_withTask() { + mTaskView.onTaskAppeared(mTaskInfo, mLeash); + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + + verify(mViewListener).onInitialized(); + verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(true)); + } + + @Test + public void testSurfaceDestroyed_noTask() { + SurfaceHolder sh = mock(SurfaceHolder.class); + mTaskView.surfaceCreated(sh); + mTaskView.surfaceDestroyed(sh); + + verify(mViewListener, never()).onTaskVisibilityChanged(anyInt(), anyBoolean()); + } + + @Test + public void testSurfaceDestroyed_withTask() { + SurfaceHolder sh = mock(SurfaceHolder.class); + mTaskView.onTaskAppeared(mTaskInfo, mLeash); + mTaskView.surfaceCreated(sh); + reset(mViewListener); + mTaskView.surfaceDestroyed(sh); + + verify(mViewListener).onTaskVisibilityChanged(eq(mTaskInfo.taskId), eq(false)); + } + + @Test + public void testOnReleased() { + mTaskView.onTaskAppeared(mTaskInfo, mLeash); + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + mTaskView.release(); + + verify(mTaskListener).removeListener(eq(mTaskView)); + verify(mViewListener).onReleased(); + } + + @Test + public void testOnTaskVanished() { + mTaskView.onTaskAppeared(mTaskInfo, mLeash); + mTaskView.surfaceCreated(mock(SurfaceHolder.class)); + mTaskView.onTaskVanished(mTaskInfo); + + verify(mViewListener).onTaskRemovalStarted(eq(mTaskInfo.taskId)); + } + + @Test + public void testOnBackPressedOnTaskRoot() { + mTaskView.onTaskAppeared(mTaskInfo, mLeash); + mTaskView.onBackPressedOnTaskRoot(mTaskInfo); + + verify(mViewListener).onBackPressedOnTaskRoot(eq(mTaskInfo.taskId)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java index 3a68ff30086e..27c6fc147772 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/TestableBubbleController.java @@ -19,6 +19,7 @@ package com.android.systemui.bubbles; import android.app.INotificationManager; import android.content.Context; import android.content.pm.LauncherApps; +import android.os.Handler; import android.view.WindowManager; import com.android.internal.statusbar.IStatusBarService; @@ -35,10 +36,10 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.phone.ShadeController; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.ZenModeController; +import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.WindowManagerShellWrapper; import com.android.wm.shell.common.FloatingContentCoordinator; - /** * Testable BubbleController subclass that immediately synchronizes surfaces. */ @@ -67,14 +68,17 @@ public class TestableBubbleController extends BubbleController { WindowManager windowManager, WindowManagerShellWrapper windowManagerShellWrapper, LauncherApps launcherApps, - BubbleLogger bubbleLogger) { + BubbleLogger bubbleLogger, + Handler mainHandler, + ShellTaskOrganizer shellTaskOrganizer) { super(context, notificationShadeWindowController, statusBarStateController, shadeController, data, Runnable::run, configurationController, interruptionStateProvider, zenModeController, lockscreenUserManager, groupManager, entryManager, notifPipeline, featureFlags, dumpManager, floatingContentCoordinator, dataRepository, sysUiState, notificationManager, statusBarService, - windowManager, windowManagerShellWrapper, launcherApps, bubbleLogger); + windowManager, windowManagerShellWrapper, launcherApps, bubbleLogger, + mainHandler, shellTaskOrganizer); setInflateSynchronously(true); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java index 9b6dd05cd80c..3494bd61b656 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/buttons/KeyButtonViewTest.java @@ -32,15 +32,11 @@ import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarBut import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_LONGPRESS; import static com.android.systemui.navigationbar.buttons.KeyButtonView.NavBarButtonEvent.NAVBAR_OVERVIEW_BUTTON_TAP; -import static junit.framework.Assert.assertEquals; - 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.hardware.input.InputManager; import android.testing.AndroidTestingRunner; @@ -151,19 +147,4 @@ public class KeyButtonViewTest extends SysuiTestCase { verify(mUiEventLogger, times(1)).log(expected); } } - - @Test - public void testBubbleEvents_bubbleExpanded() { - when(mBubbles.getExpandedDisplayId(mContext)).thenReturn(3); - - int action = KeyEvent.ACTION_DOWN; - int flags = 0; - int code = KeyEvent.KEYCODE_BACK; - mKeyButtonView.setCode(code); - mKeyButtonView.sendEvent(action, flags); - - verify(mInputManager, times(1)).injectInputEvent(mInputEventCaptor.capture(), - anyInt()); - assertEquals(3, mInputEventCaptor.getValue().getDisplayId()); - } }
\ No newline at end of file |