Merge "Fix launching bubbles from widgets" into sc-dev
This commit is contained in:
@@ -368,6 +368,13 @@ public class BubbleController {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (Bubble b : mBubbleData.getOverflowBubbles()) {
|
||||||
|
if (task.taskId == b.getTaskId()) {
|
||||||
|
promoteBubbleFromOverflow(b);
|
||||||
|
mBubbleData.setExpanded(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -815,7 +822,35 @@ public class BubbleController {
|
|||||||
setIsBubble(bubble, true /* isBubble */);
|
setIsBubble(bubble, true /* isBubble */);
|
||||||
}
|
}
|
||||||
|
|
||||||
@VisibleForTesting
|
/**
|
||||||
|
* Expands and selects the provided bubble as long as it already exists in the stack or the
|
||||||
|
* overflow.
|
||||||
|
*
|
||||||
|
* This is currently only used when opening a bubble via clicking on a conversation widget.
|
||||||
|
*/
|
||||||
|
public void expandStackAndSelectBubble(Bubble b) {
|
||||||
|
if (b == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) {
|
||||||
|
// already in the stack
|
||||||
|
mBubbleData.setSelectedBubble(b);
|
||||||
|
mBubbleData.setExpanded(true);
|
||||||
|
} else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) {
|
||||||
|
// promote it out of the overflow
|
||||||
|
promoteBubbleFromOverflow(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble
|
||||||
|
* exists for this entry, and it is able to bubble, a new bubble will be created.
|
||||||
|
*
|
||||||
|
* This is the method to use when opening a bubble via a notification or in a state where
|
||||||
|
* the device might not be unlocked.
|
||||||
|
*
|
||||||
|
* @param entry the entry to use for the bubble.
|
||||||
|
*/
|
||||||
public void expandStackAndSelectBubble(BubbleEntry entry) {
|
public void expandStackAndSelectBubble(BubbleEntry entry) {
|
||||||
if (mIsStatusBarShade) {
|
if (mIsStatusBarShade) {
|
||||||
mNotifEntryToExpandOnShadeUnlock = null;
|
mNotifEntryToExpandOnShadeUnlock = null;
|
||||||
@@ -1382,6 +1417,21 @@ public class BubbleController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void expandStackAndSelectBubble(Bubble bubble) {
|
||||||
|
mMainExecutor.execute(() -> {
|
||||||
|
BubbleController.this.expandStackAndSelectBubble(bubble);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
public Bubble getBubbleWithShortcutId(String shortcutId) {
|
||||||
|
return mMainExecutor.executeBlockingForResult(() -> {
|
||||||
|
return BubbleController.this.mBubbleData.getAnyBubbleWithShortcutId(shortcutId);
|
||||||
|
}, Bubble.class);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTaskbarChanged(Bundle b) {
|
public void onTaskbarChanged(Bundle b) {
|
||||||
mMainExecutor.execute(() -> {
|
mMainExecutor.execute(() -> {
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import android.app.PendingIntent;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.LocusId;
|
import android.content.LocusId;
|
||||||
import android.content.pm.ShortcutInfo;
|
import android.content.pm.ShortcutInfo;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.ArrayMap;
|
import android.util.ArrayMap;
|
||||||
import android.util.ArraySet;
|
import android.util.ArraySet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@@ -874,6 +875,34 @@ public class BubbleData {
|
|||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return any bubble (in the stack or the overflow) that matches the provided shortcutId. */
|
||||||
|
@Nullable
|
||||||
|
Bubble getAnyBubbleWithShortcutId(String shortcutId) {
|
||||||
|
if (TextUtils.isEmpty(shortcutId)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < mBubbles.size(); i++) {
|
||||||
|
Bubble bubble = mBubbles.get(i);
|
||||||
|
String bubbleShortcutId = bubble.getShortcutInfo() != null
|
||||||
|
? bubble.getShortcutInfo().getId()
|
||||||
|
: bubble.getMetadataShortcutId();
|
||||||
|
if (shortcutId.equals(bubbleShortcutId)) {
|
||||||
|
return bubble;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < mOverflowBubbles.size(); i++) {
|
||||||
|
Bubble bubble = mOverflowBubbles.get(i);
|
||||||
|
String bubbleShortcutId = bubble.getShortcutInfo() != null
|
||||||
|
? bubble.getShortcutInfo().getId()
|
||||||
|
: bubble.getMetadataShortcutId();
|
||||||
|
if (shortcutId.equals(bubbleShortcutId)) {
|
||||||
|
return bubble;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
@VisibleForTesting(visibility = PRIVATE)
|
@VisibleForTesting(visibility = PRIVATE)
|
||||||
@Nullable
|
@Nullable
|
||||||
public Bubble getBubbleInStackWithKey(String key) {
|
public Bubble getBubbleInStackWithKey(String key) {
|
||||||
|
|||||||
@@ -118,6 +118,19 @@ public interface Bubbles {
|
|||||||
*/
|
*/
|
||||||
void expandStackAndSelectBubble(BubbleEntry entry);
|
void expandStackAndSelectBubble(BubbleEntry entry);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the stack expand if needed, then select the specified Bubble as current.
|
||||||
|
*
|
||||||
|
* @param bubble the bubble to be selected
|
||||||
|
*/
|
||||||
|
void expandStackAndSelectBubble(Bubble bubble);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a bubble that matches the provided shortcutId, if one exists.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
Bubble getBubbleWithShortcutId(String shortcutId);
|
||||||
|
|
||||||
/** Called for any taskbar changes. */
|
/** Called for any taskbar changes. */
|
||||||
void onTaskbarChanged(Bundle b);
|
void onTaskbarChanged(Bundle b);
|
||||||
|
|
||||||
|
|||||||
@@ -35,9 +35,11 @@ import com.android.internal.logging.UiEventLoggerImpl;
|
|||||||
import com.android.internal.statusbar.IStatusBarService;
|
import com.android.internal.statusbar.IStatusBarService;
|
||||||
import com.android.internal.statusbar.NotificationVisibility;
|
import com.android.internal.statusbar.NotificationVisibility;
|
||||||
import com.android.systemui.people.PeopleSpaceUtils;
|
import com.android.systemui.people.PeopleSpaceUtils;
|
||||||
|
import com.android.systemui.statusbar.CommandQueue;
|
||||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||||
import com.android.systemui.wmshell.BubblesManager;
|
import com.android.systemui.wmshell.BubblesManager;
|
||||||
|
import com.android.wm.shell.bubbles.Bubble;
|
||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
@@ -53,14 +55,35 @@ public class LaunchConversationActivity extends Activity {
|
|||||||
private final UserManager mUserManager;
|
private final UserManager mUserManager;
|
||||||
private boolean mIsForTesting;
|
private boolean mIsForTesting;
|
||||||
private IStatusBarService mIStatusBarService;
|
private IStatusBarService mIStatusBarService;
|
||||||
|
private CommandQueue mCommandQueue;
|
||||||
|
private Bubble mBubble;
|
||||||
|
private NotificationEntry mEntryToBubble;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public LaunchConversationActivity(NotificationEntryManager notificationEntryManager,
|
public LaunchConversationActivity(NotificationEntryManager notificationEntryManager,
|
||||||
Optional<BubblesManager> bubblesManagerOptional, UserManager userManager) {
|
Optional<BubblesManager> bubblesManagerOptional, UserManager userManager,
|
||||||
|
CommandQueue commandQueue) {
|
||||||
super();
|
super();
|
||||||
mNotificationEntryManager = notificationEntryManager;
|
mNotificationEntryManager = notificationEntryManager;
|
||||||
mBubblesManagerOptional = bubblesManagerOptional;
|
mBubblesManagerOptional = bubblesManagerOptional;
|
||||||
mUserManager = userManager;
|
mUserManager = userManager;
|
||||||
|
mCommandQueue = commandQueue;
|
||||||
|
mCommandQueue.addCallback(new CommandQueue.Callbacks() {
|
||||||
|
// (b/190833924) Wait for the app transition to finish before showing the bubble,
|
||||||
|
// opening the bubble while the transition is happening can mess with the placement
|
||||||
|
// of the bubble's surface.
|
||||||
|
@Override
|
||||||
|
public void appTransitionFinished(int displayId) {
|
||||||
|
if (mBubblesManagerOptional.isPresent()) {
|
||||||
|
if (mBubble != null) {
|
||||||
|
mBubblesManagerOptional.get().expandStackAndSelectBubble(mBubble);
|
||||||
|
} else if (mEntryToBubble != null) {
|
||||||
|
mBubblesManagerOptional.get().expandStackAndSelectBubble(mEntryToBubble);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mCommandQueue.removeCallback(this);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -95,14 +118,28 @@ public class LaunchConversationActivity extends Activity {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
|
// We can potentially bubble without a notification, so rather than rely on
|
||||||
notificationKey);
|
// notificationKey here (which could be null if there's no notification or if the
|
||||||
if (entry != null && entry.canBubble() && mBubblesManagerOptional.isPresent()) {
|
// bubble is suppressing the notification), so we'll use the shortcutId for lookups.
|
||||||
if (DEBUG) Log.d(TAG, "Open bubble for conversation");
|
// This misses one specific case: a bubble that was never opened & still has a
|
||||||
mBubblesManagerOptional.get().expandStackAndSelectBubble(entry);
|
// visible notification, but the bubble was dismissed & aged out of the overflow.
|
||||||
// Just opt-out and don't cancel the notification for bubbles.
|
// So it wouldn't exist in the stack or overflow to be looked up BUT the notif entry
|
||||||
finish();
|
// would still exist & be bubbleable. So if we don't get a bubble from the
|
||||||
return;
|
// shortcutId, fallback to notificationKey if it exists.
|
||||||
|
if (mBubblesManagerOptional.isPresent()) {
|
||||||
|
mBubble = mBubblesManagerOptional.get().getBubbleWithShortcutId(tileId);
|
||||||
|
NotificationEntry entry = mNotificationEntryManager.getPendingOrActiveNotif(
|
||||||
|
notificationKey);
|
||||||
|
if (mBubble != null || (entry != null && entry.canBubble())) {
|
||||||
|
mEntryToBubble = entry;
|
||||||
|
if (DEBUG) {
|
||||||
|
Log.d(TAG,
|
||||||
|
"Opening bubble: " + mBubble + ", entry: " + mEntryToBubble);
|
||||||
|
}
|
||||||
|
// Just opt-out and don't cancel the notification for bubbles.
|
||||||
|
finish();
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mIStatusBarService == null) {
|
if (mIStatusBarService == null) {
|
||||||
|
|||||||
@@ -84,6 +84,7 @@ import com.android.systemui.statusbar.phone.ScrimController;
|
|||||||
import com.android.systemui.statusbar.phone.ShadeController;
|
import com.android.systemui.statusbar.phone.ShadeController;
|
||||||
import com.android.systemui.statusbar.policy.ConfigurationController;
|
import com.android.systemui.statusbar.policy.ConfigurationController;
|
||||||
import com.android.systemui.statusbar.policy.ZenModeController;
|
import com.android.systemui.statusbar.policy.ZenModeController;
|
||||||
|
import com.android.wm.shell.bubbles.Bubble;
|
||||||
import com.android.wm.shell.bubbles.BubbleEntry;
|
import com.android.wm.shell.bubbles.BubbleEntry;
|
||||||
import com.android.wm.shell.bubbles.Bubbles;
|
import com.android.wm.shell.bubbles.Bubbles;
|
||||||
|
|
||||||
@@ -657,6 +658,22 @@ public class BubblesManager implements Dumpable {
|
|||||||
mBubbles.expandStackAndSelectBubble(notifToBubbleEntry(entry));
|
mBubbles.expandStackAndSelectBubble(notifToBubbleEntry(entry));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request the stack expand if needed, then select the specified Bubble as current.
|
||||||
|
*
|
||||||
|
* @param bubble the bubble to be selected
|
||||||
|
*/
|
||||||
|
public void expandStackAndSelectBubble(Bubble bubble) {
|
||||||
|
mBubbles.expandStackAndSelectBubble(bubble);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return a bubble that matches the provided shortcutId, if one exists.
|
||||||
|
*/
|
||||||
|
public Bubble getBubbleWithShortcutId(String shortcutId) {
|
||||||
|
return mBubbles.getBubbleWithShortcutId(shortcutId);
|
||||||
|
}
|
||||||
|
|
||||||
/** See {@link NotifCallback}. */
|
/** See {@link NotifCallback}. */
|
||||||
public void addNotifCallback(NotifCallback callback) {
|
public void addNotifCallback(NotifCallback callback) {
|
||||||
mCallbacks.add(callback);
|
mCallbacks.add(callback);
|
||||||
|
|||||||
@@ -16,11 +16,14 @@
|
|||||||
|
|
||||||
package com.android.systemui.people.widget;
|
package com.android.systemui.people.widget;
|
||||||
|
|
||||||
|
import static android.view.Display.DEFAULT_DISPLAY;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@@ -39,9 +42,11 @@ import androidx.test.filters.SmallTest;
|
|||||||
import com.android.internal.statusbar.IStatusBarService;
|
import com.android.internal.statusbar.IStatusBarService;
|
||||||
import com.android.internal.statusbar.NotificationVisibility;
|
import com.android.internal.statusbar.NotificationVisibility;
|
||||||
import com.android.systemui.SysuiTestCase;
|
import com.android.systemui.SysuiTestCase;
|
||||||
|
import com.android.systemui.statusbar.CommandQueue;
|
||||||
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
import com.android.systemui.statusbar.notification.NotificationEntryManager;
|
||||||
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
|
||||||
import com.android.systemui.wmshell.BubblesManager;
|
import com.android.systemui.wmshell.BubblesManager;
|
||||||
|
import com.android.wm.shell.bubbles.Bubble;
|
||||||
|
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@@ -87,6 +92,8 @@ public class LaunchConversationActivityTest extends SysuiTestCase {
|
|||||||
@Mock
|
@Mock
|
||||||
private UserManager mUserManager;
|
private UserManager mUserManager;
|
||||||
|
|
||||||
|
private CommandQueue mCommandQueue;
|
||||||
|
|
||||||
@Captor
|
@Captor
|
||||||
private ArgumentCaptor<NotificationVisibility> mNotificationVisibilityCaptor;
|
private ArgumentCaptor<NotificationVisibility> mNotificationVisibilityCaptor;
|
||||||
|
|
||||||
@@ -95,8 +102,9 @@ public class LaunchConversationActivityTest extends SysuiTestCase {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
MockitoAnnotations.initMocks(this);
|
MockitoAnnotations.initMocks(this);
|
||||||
|
mCommandQueue = new CommandQueue(mContext);
|
||||||
mActivity = new LaunchConversationActivity(mNotificationEntryManager,
|
mActivity = new LaunchConversationActivity(mNotificationEntryManager,
|
||||||
Optional.of(mBubblesManager), mUserManager);
|
Optional.of(mBubblesManager), mUserManager, mCommandQueue);
|
||||||
mActivity.setIsForTesting(true, mIStatusBarService);
|
mActivity.setIsForTesting(true, mIStatusBarService);
|
||||||
mIntent = new Intent();
|
mIntent = new Intent();
|
||||||
mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, "tile ID");
|
mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, "tile ID");
|
||||||
@@ -159,9 +167,13 @@ public class LaunchConversationActivityTest extends SysuiTestCase {
|
|||||||
mActivity.setIntent(mIntent);
|
mActivity.setIntent(mIntent);
|
||||||
mActivity.onCreate(new Bundle());
|
mActivity.onCreate(new Bundle());
|
||||||
|
|
||||||
|
assertThat(mActivity.isFinishing()).isTrue();
|
||||||
|
mCommandQueue.appTransitionFinished(DEFAULT_DISPLAY);
|
||||||
|
|
||||||
verify(mIStatusBarService, times(1)).onNotificationClear(any(),
|
verify(mIStatusBarService, times(1)).onNotificationClear(any(),
|
||||||
anyInt(), any(), anyInt(), anyInt(), mNotificationVisibilityCaptor.capture());
|
anyInt(), any(), anyInt(), anyInt(), mNotificationVisibilityCaptor.capture());
|
||||||
verify(mBubblesManager, never()).expandStackAndSelectBubble(any());
|
verify(mBubblesManager, never()).expandStackAndSelectBubble(any(Bubble.class));
|
||||||
|
verify(mBubblesManager, never()).expandStackAndSelectBubble(any(NotificationEntry.class));
|
||||||
|
|
||||||
NotificationVisibility nv = mNotificationVisibilityCaptor.getValue();
|
NotificationVisibility nv = mNotificationVisibilityCaptor.getValue();
|
||||||
assertThat(nv.count).isEqualTo(NOTIF_COUNT);
|
assertThat(nv.count).isEqualTo(NOTIF_COUNT);
|
||||||
@@ -175,6 +187,9 @@ public class LaunchConversationActivityTest extends SysuiTestCase {
|
|||||||
mActivity.setIntent(mIntent);
|
mActivity.setIntent(mIntent);
|
||||||
mActivity.onCreate(new Bundle());
|
mActivity.onCreate(new Bundle());
|
||||||
|
|
||||||
|
assertThat(mActivity.isFinishing()).isTrue();
|
||||||
|
mCommandQueue.appTransitionFinished(DEFAULT_DISPLAY);
|
||||||
|
|
||||||
// Don't clear the notification for bubbles.
|
// Don't clear the notification for bubbles.
|
||||||
verify(mIStatusBarService, never()).onNotificationClear(any(),
|
verify(mIStatusBarService, never()).onNotificationClear(any(),
|
||||||
anyInt(), any(), anyInt(), anyInt(), any());
|
anyInt(), any(), anyInt(), anyInt(), any());
|
||||||
@@ -190,8 +205,27 @@ public class LaunchConversationActivityTest extends SysuiTestCase {
|
|||||||
mActivity.onCreate(new Bundle());
|
mActivity.onCreate(new Bundle());
|
||||||
|
|
||||||
assertThat(mActivity.isFinishing()).isTrue();
|
assertThat(mActivity.isFinishing()).isTrue();
|
||||||
|
mCommandQueue.appTransitionFinished(DEFAULT_DISPLAY);
|
||||||
|
|
||||||
verify(mIStatusBarService, never()).onNotificationClear(any(),
|
verify(mIStatusBarService, never()).onNotificationClear(any(),
|
||||||
anyInt(), any(), anyInt(), anyInt(), any());
|
anyInt(), any(), anyInt(), anyInt(), any());
|
||||||
verify(mBubblesManager, never()).expandStackAndSelectBubble(any());
|
verify(mBubblesManager, never()).expandStackAndSelectBubble(any(Bubble.class));
|
||||||
|
verify(mBubblesManager, never()).expandStackAndSelectBubble(any(NotificationEntry.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testBubbleWithNoNotifOpensBubble() throws Exception {
|
||||||
|
Bubble bubble = mock(Bubble.class);
|
||||||
|
when(mBubblesManager.getBubbleWithShortcutId(any())).thenReturn(bubble);
|
||||||
|
|
||||||
|
mIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_NOTIFICATION_KEY,
|
||||||
|
EMPTY_STRING);
|
||||||
|
mActivity.setIntent(mIntent);
|
||||||
|
mActivity.onCreate(new Bundle());
|
||||||
|
|
||||||
|
assertThat(mActivity.isFinishing()).isTrue();
|
||||||
|
mCommandQueue.appTransitionFinished(DEFAULT_DISPLAY);
|
||||||
|
|
||||||
|
verify(mBubblesManager, times(1)).expandStackAndSelectBubble(eq(bubble));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user