Bubbles & TaskOrganizer
Move bubbles off of ActivityView & onto TaskOrganizer. This mostly impacts BubbleExpandedView, there are a couple of differences spawning from that: * taskId is the new thing, displayId's are old news * No longer need to send back events to the bubble (we can set the bubble'd task to intercept now). * Now we have to subtract the touch region of the activity content * IME insets don't work the "normal" way (?) instead we're now using PinnedStackListener for all our IME needs BubbleTaskView is the ActivityView replacement & it uses BubbleTaskManager internally to deal with the tasks. Test: atest BubbleControllerTest BubbleDataTest (existing) Test: atest MultiWindowTaskListenerTest TaskViewTest Bug: 148977538 Change-Id: Id6e3bf4950218036b1626481c846ffe57b9ef857
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -489,6 +481,14 @@ class Bubble implements BubbleViewProvider {
|
||||
return mNotificationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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).
|
||||
*/
|
||||
|
||||
@@ -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. */
|
||||
@@ -889,35 +900,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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 int mTaskId = INVALID_TASK_ID;
|
||||
|
||||
private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING;
|
||||
private int mTaskId = -1;
|
||||
|
||||
private PendingIntent mPendingIntent;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
mActivitySurface.setZOrderedOnTop(onTop, true);
|
||||
mTaskView.setZOrderedOnTop(onTop, true /* allowDynamicChange */);
|
||||
}
|
||||
|
||||
/** Return a GraphicBuffer with the contents of the ActivityView's underlying surface. */
|
||||
void setImeVisible(boolean visible) {
|
||||
mImeVisible = visible;
|
||||
if (!mImeVisible && mNeedsNewHeight) {
|
||||
updateHeight();
|
||||
}
|
||||
}
|
||||
|
||||
/** 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());
|
||||
Log.d(TAG, "cleanUpExpandedState: bubble=" + getBubbleKey() + " task=" + mTaskId);
|
||||
}
|
||||
if (mActivityView == null) {
|
||||
return;
|
||||
if (mTaskView != null) {
|
||||
mTaskView.release();
|
||||
}
|
||||
mActivityView.release();
|
||||
if (mTaskId != -1) {
|
||||
try {
|
||||
ActivityTaskManager.getService().removeTask(mTaskId);
|
||||
} catch (RemoteException e) {
|
||||
Log.w(TAG, "Failed to remove taskId " + mTaskId);
|
||||
}
|
||||
mTaskId = -1;
|
||||
if (mTaskView != null) {
|
||||
removeView(mTaskView);
|
||||
mTaskView = null;
|
||||
}
|
||||
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);
|
||||
}
|
||||
if (mActivityViewStatus == ActivityViewStatus.ACTIVITY_STARTED) {
|
||||
mActivityViewStatus = ActivityViewStatus.INITIALIZED;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
@@ -1443,13 +1438,6 @@ public class BubbleStackView extends FrameLayout
|
||||
return mIsExpansionAnimating;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@@ -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,10 +1755,9 @@ 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<>();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -48,5 +48,5 @@ interface BubbleViewProvider {
|
||||
|
||||
boolean showDot();
|
||||
|
||||
int getDisplayId();
|
||||
int getTaskId();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
308
packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java
Normal file
308
packages/SystemUI/src/com/android/systemui/bubbles/TaskView.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user