Merge "Bubbles & TaskOrganizer"

This commit is contained in:
Mady Mellor
2020-10-13 20:05:55 +00:00
committed by Android (Google) Code Review
21 changed files with 1143 additions and 786 deletions

View File

@@ -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).
*/

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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 {

View File

@@ -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<>();

View File

@@ -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();
}
}

View File

@@ -48,5 +48,5 @@ interface BubbleViewProvider {
boolean showDot();
int getDisplayId();
int getTaskId();
}

View File

@@ -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();
}

View File

@@ -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));
}
}

View 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();
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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

View File

@@ -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());
}
}

View File

@@ -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);

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}