Merge "Bubble overflow - core functionality"
This commit is contained in:
committed by
Android (Google) Code Review
commit
3fd3a91005
23
packages/SystemUI/res/drawable/ic_bubble_overflow_button.xml
Normal file
23
packages/SystemUI/res/drawable/ic_bubble_overflow_button.xml
Normal file
@@ -0,0 +1,23 @@
|
||||
<!--
|
||||
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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:height="52dp"
|
||||
android:width="52dp">
|
||||
<path android:fillColor="#1A73E8"
|
||||
android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
|
||||
</vector>
|
||||
@@ -13,8 +13,9 @@
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License
|
||||
-->
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/bubble_overflow_recycler"
|
||||
android:scrollbars="vertical"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"/>
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
24
packages/SystemUI/res/layout/bubble_overflow_button.xml
Normal file
24
packages/SystemUI/res/layout/bubble_overflow_button.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
~ 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
|
||||
-->
|
||||
<ImageView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/bubble_overflow_button"
|
||||
android:layout_width="@dimen/individual_bubble_size"
|
||||
android:layout_height="@dimen/individual_bubble_size"
|
||||
android:src="@drawable/ic_bubble_overflow_button"
|
||||
android:scaleType="center"
|
||||
android:layout_gravity="end"/>
|
||||
@@ -27,6 +27,12 @@
|
||||
performance issues arise. -->
|
||||
<integer name="bubbles_max_rendered">5</integer>
|
||||
|
||||
<!-- Number of columns in bubble overflow. -->
|
||||
<integer name="bubbles_overflow_columns">4</integer>
|
||||
|
||||
<!-- Maximum number of bubbles we allow in overflow before we dismiss the oldest one. -->
|
||||
<integer name="bubbles_max_overflow">16</integer>
|
||||
|
||||
<!-- Ratio of "left" end of status bar that will swipe to QQS. -->
|
||||
<integer name="qqs_split_fraction">3</integer>
|
||||
<!-- Ratio of "right" end of status bar that will swipe to QS. -->
|
||||
|
||||
@@ -89,12 +89,12 @@ public class BadgedImageView extends ImageView {
|
||||
/**
|
||||
* Updates the view with provided info.
|
||||
*/
|
||||
public void update(Bubble bubble, Bitmap bubbleImage, int dotColor, Path dotPath) {
|
||||
public void update(Bubble bubble) {
|
||||
mBubble = bubble;
|
||||
setImageBitmap(bubbleImage);
|
||||
setImageBitmap(bubble.getBadgedImage());
|
||||
setDotState(DOT_STATE_SUPPRESSED_FOR_FLYOUT);
|
||||
mDotColor = dotColor;
|
||||
drawDot(dotPath);
|
||||
mDotColor = bubble.getDotColor();
|
||||
drawDot(bubble.getDotPath());
|
||||
animateDot();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ import android.content.pm.LauncherApps;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Bundle;
|
||||
@@ -93,6 +95,9 @@ class Bubble {
|
||||
}
|
||||
|
||||
private FlyoutMessage mFlyoutMessage;
|
||||
private Bitmap mBadgedImage;
|
||||
private int mDotColor;
|
||||
private Path mDotPath;
|
||||
|
||||
public static String groupId(NotificationEntry entry) {
|
||||
UserHandle user = entry.getSbn().getUser();
|
||||
@@ -124,6 +129,18 @@ class Bubble {
|
||||
return mEntry.getSbn().getPackageName();
|
||||
}
|
||||
|
||||
public Bitmap getBadgedImage() {
|
||||
return mBadgedImage;
|
||||
}
|
||||
|
||||
public int getDotColor() {
|
||||
return mDotColor;
|
||||
}
|
||||
|
||||
public Path getDotPath() {
|
||||
return mDotPath;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getAppName() {
|
||||
return mAppName;
|
||||
@@ -205,8 +222,12 @@ class Bubble {
|
||||
mAppName = info.appName;
|
||||
mFlyoutMessage = info.flyoutMessage;
|
||||
|
||||
mBadgedImage = info.badgedBubbleImage;
|
||||
mDotColor = info.dotColor;
|
||||
mDotPath = info.dotPath;
|
||||
|
||||
mExpandedView.update(this);
|
||||
mIconView.update(this, info.badgedBubbleImage, info.dotColor, info.dotPath);
|
||||
mIconView.update(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -261,6 +282,13 @@ class Bubble {
|
||||
setShowDot(false /* show */, true /* animate */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be invoked whenever a Bubble is promoted from overflow.
|
||||
*/
|
||||
void markUpdatedAt(long lastAccessedMillis) {
|
||||
mLastUpdated = lastAccessedMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this notification should be shown in the shade when it is also displayed as a
|
||||
* bubble.
|
||||
|
||||
@@ -103,6 +103,7 @@ import java.lang.annotation.Target;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
@@ -151,6 +152,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
private BubbleData mBubbleData;
|
||||
@Nullable private BubbleStackView mStackView;
|
||||
private BubbleIconFactory mBubbleIconFactory;
|
||||
private int mMaxBubbles;
|
||||
|
||||
// Tracks the id of the current (foreground) user.
|
||||
private int mCurrentUserId;
|
||||
@@ -171,6 +173,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
private StatusBarStateListener mStatusBarStateListener;
|
||||
private final ScreenshotHelper mScreenshotHelper;
|
||||
|
||||
// Callback that updates BubbleOverflowActivity on data change.
|
||||
@Nullable private Runnable mOverflowCallback = null;
|
||||
|
||||
private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
|
||||
private IStatusBarService mBarService;
|
||||
@@ -301,6 +305,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
|
||||
mBubbleData = data;
|
||||
mBubbleData.setListener(mBubbleDataListener);
|
||||
mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
|
||||
|
||||
mNotificationEntryManager = entryManager;
|
||||
mNotificationEntryManager.addNotificationEntryListener(mEntryListener);
|
||||
@@ -370,6 +375,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
mInflateSynchronously = inflateSynchronously;
|
||||
}
|
||||
|
||||
void setOverflowCallback(Runnable updateOverflow) {
|
||||
mOverflowCallback = updateOverflow;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Bubbles for updating overflow.
|
||||
*/
|
||||
List<Bubble> getOverflowBubbles() {
|
||||
return mBubbleData.getOverflowBubbles();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -537,6 +554,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
mBubbleData.setSelectedBubble(bubble);
|
||||
}
|
||||
|
||||
void promoteBubbleFromOverflow(Bubble bubble) {
|
||||
mBubbleData.promoteBubbleFromOverflow(bubble);
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the stack expand if needed, then select the specified Bubble as current.
|
||||
*
|
||||
@@ -817,6 +838,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
|
||||
@Override
|
||||
public void applyUpdate(BubbleData.Update update) {
|
||||
// Update bubbles in overflow.
|
||||
if (mOverflowCallback != null) {
|
||||
mOverflowCallback.run();
|
||||
}
|
||||
|
||||
if (update.addedBubble != null) {
|
||||
mStackView.addBubble(update.addedBubble);
|
||||
}
|
||||
@@ -890,6 +916,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
mStackView.updateBubble(update.updatedBubble);
|
||||
}
|
||||
|
||||
// At this point, the correct bubbles are inflated in the stack.
|
||||
// Make sure the order in bubble data is reflected in bubble row.
|
||||
if (update.orderChanged) {
|
||||
mStackView.updateBubbleOrder(update.bubbles);
|
||||
}
|
||||
@@ -912,15 +940,18 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
updateStack();
|
||||
|
||||
if (DEBUG_BUBBLE_CONTROLLER) {
|
||||
Log.d(TAG, "[BubbleData]");
|
||||
Log.d(TAG, "\n[BubbleData] bubbles:");
|
||||
Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getBubbles(),
|
||||
mBubbleData.getSelectedBubble()));
|
||||
|
||||
if (mStackView != null) {
|
||||
Log.d(TAG, "[BubbleStackView]");
|
||||
Log.d(TAG, "\n[BubbleStackView]");
|
||||
Log.d(TAG, BubbleDebugConfig.formatBubblesString(mStackView.getBubblesOnScreen(),
|
||||
mStackView.getExpandedBubble()));
|
||||
}
|
||||
Log.d(TAG, "\n[BubbleData] overflow:");
|
||||
Log.d(TAG, BubbleDebugConfig.formatBubblesString(mBubbleData.getOverflowBubbles(),
|
||||
null));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -78,9 +78,11 @@ public class BubbleData {
|
||||
|
||||
// A read-only view of the bubbles list, changes there will be reflected here.
|
||||
final List<Bubble> bubbles;
|
||||
final List<Bubble> overflowBubbles;
|
||||
|
||||
private Update(List<Bubble> bubbleOrder) {
|
||||
bubbles = Collections.unmodifiableList(bubbleOrder);
|
||||
private Update(List<Bubble> row, List<Bubble> overflow) {
|
||||
bubbles = Collections.unmodifiableList(row);
|
||||
overflowBubbles = Collections.unmodifiableList(overflow);
|
||||
}
|
||||
|
||||
boolean anythingChanged() {
|
||||
@@ -113,11 +115,14 @@ public class BubbleData {
|
||||
private final Context mContext;
|
||||
/** Bubbles that are actively in the stack. */
|
||||
private final List<Bubble> mBubbles;
|
||||
/** Bubbles that aged out to overflow. */
|
||||
private final List<Bubble> mOverflowBubbles;
|
||||
/** Bubbles that are being loaded but haven't been added to the stack just yet. */
|
||||
private final List<Bubble> mPendingBubbles;
|
||||
private Bubble mSelectedBubble;
|
||||
private boolean mExpanded;
|
||||
private final int mMaxBubbles;
|
||||
private final int mMaxOverflowBubbles;
|
||||
|
||||
// State tracked during an operation -- keeps track of what listener events to dispatch.
|
||||
private Update mStateChange;
|
||||
@@ -146,9 +151,11 @@ public class BubbleData {
|
||||
public BubbleData(Context context) {
|
||||
mContext = context;
|
||||
mBubbles = new ArrayList<>();
|
||||
mOverflowBubbles = new ArrayList<>();
|
||||
mPendingBubbles = new ArrayList<>();
|
||||
mStateChange = new Update(mBubbles);
|
||||
mStateChange = new Update(mBubbles, mOverflowBubbles);
|
||||
mMaxBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_rendered);
|
||||
mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow);
|
||||
}
|
||||
|
||||
public boolean hasBubbles() {
|
||||
@@ -184,6 +191,19 @@ public class BubbleData {
|
||||
dispatchPendingChanges();
|
||||
}
|
||||
|
||||
public void promoteBubbleFromOverflow(Bubble bubble) {
|
||||
if (DEBUG_BUBBLE_DATA) {
|
||||
Log.d(TAG, "promoteBubbleFromOverflow: " + bubble);
|
||||
}
|
||||
mOverflowBubbles.remove(bubble);
|
||||
doAdd(bubble);
|
||||
setSelectedBubbleInternal(bubble);
|
||||
// Preserve new order for next repack, which sorts by last updated time.
|
||||
bubble.markUpdatedAt(mTimeSource.currentTimeMillis());
|
||||
trim();
|
||||
dispatchPendingChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new bubble or returns an existing one. Does not add new bubbles to
|
||||
* bubble data, must go through {@link #notificationEntryUpdated(Bubble, boolean, boolean)}
|
||||
@@ -343,6 +363,7 @@ public class BubbleData {
|
||||
mStateChange.orderChanged = true;
|
||||
}
|
||||
mStateChange.addedBubble = bubble;
|
||||
|
||||
if (!isExpanded()) {
|
||||
mStateChange.orderChanged |= packGroup(findFirstIndexForGroup(bubble.getGroupId()));
|
||||
// Top bubble becomes selected.
|
||||
@@ -407,6 +428,17 @@ public class BubbleData {
|
||||
mStateChange.orderChanged |= repackAll();
|
||||
}
|
||||
|
||||
if (reason == BubbleController.DISMISS_AGED) {
|
||||
if (DEBUG_BUBBLE_DATA) {
|
||||
Log.d(TAG, "overflowing bubble: " + bubbleToRemove);
|
||||
}
|
||||
mOverflowBubbles.add(0, bubbleToRemove);
|
||||
if (mOverflowBubbles.size() == mMaxOverflowBubbles + 1) {
|
||||
// Remove oldest bubble.
|
||||
mOverflowBubbles.remove(mOverflowBubbles.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Note: If mBubbles.isEmpty(), then mSelectedBubble is now null.
|
||||
if (Objects.equals(mSelectedBubble, bubbleToRemove)) {
|
||||
// Move selection to the new bubble at the same position.
|
||||
@@ -454,7 +486,7 @@ public class BubbleData {
|
||||
if (mListener != null && mStateChange.anythingChanged()) {
|
||||
mListener.applyUpdate(mStateChange);
|
||||
}
|
||||
mStateChange = new Update(mBubbles);
|
||||
mStateChange = new Update(mBubbles, mOverflowBubbles);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -689,12 +721,19 @@ public class BubbleData {
|
||||
}
|
||||
|
||||
/**
|
||||
* The set of bubbles.
|
||||
* The set of bubbles in row.
|
||||
*/
|
||||
@VisibleForTesting(visibility = PRIVATE)
|
||||
public List<Bubble> getBubbles() {
|
||||
return Collections.unmodifiableList(mBubbles);
|
||||
}
|
||||
/**
|
||||
* The set of bubbles in overflow.
|
||||
*/
|
||||
@VisibleForTesting(visibility = PRIVATE)
|
||||
public List<Bubble> getOverflowBubbles() {
|
||||
return Collections.unmodifiableList(mOverflowBubbles);
|
||||
}
|
||||
|
||||
@VisibleForTesting(visibility = PRIVATE)
|
||||
Bubble getBubbleWithKey(String key) {
|
||||
|
||||
@@ -24,6 +24,7 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPAND
|
||||
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
|
||||
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.ActivityOptions;
|
||||
import android.app.ActivityTaskManager;
|
||||
import android.app.ActivityView;
|
||||
@@ -83,7 +84,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
|
||||
private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING;
|
||||
private int mTaskId = -1;
|
||||
|
||||
private PendingIntent mBubbleIntent;
|
||||
private PendingIntent mPendingIntent;
|
||||
|
||||
private boolean mKeyboardVisible;
|
||||
private boolean mNeedsNewHeight;
|
||||
@@ -98,7 +99,9 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
|
||||
private int[] mTempLoc = new int[2];
|
||||
private int mExpandedViewTouchSlop;
|
||||
|
||||
private Bubble mBubble;
|
||||
@Nullable private Bubble mBubble;
|
||||
|
||||
private boolean mIsOverflow;
|
||||
|
||||
private BubbleController mBubbleController = Dependency.get(BubbleController.class);
|
||||
private WindowManager mWindowManager;
|
||||
@@ -125,7 +128,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
|
||||
+ "bubble=" + getBubbleKey());
|
||||
}
|
||||
try {
|
||||
if (mBubble.usingShortcutInfo()) {
|
||||
if (!mIsOverflow && mBubble.usingShortcutInfo()) {
|
||||
mActivityView.startShortcutActivity(mBubble.getShortcutInfo(),
|
||||
options, null /* sourceBounds */);
|
||||
} else {
|
||||
@@ -133,7 +136,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
|
||||
// Apply flags to make behaviour match documentLaunchMode=always.
|
||||
fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||
fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
|
||||
mActivityView.startActivity(mBubbleIntent, fillInIntent, options);
|
||||
mActivityView.startActivity(mPendingIntent, fillInIntent, options);
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
// If there's a runtime exception here then there's something
|
||||
@@ -141,7 +144,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
|
||||
// the bubble again so we'll just remove it.
|
||||
Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey()
|
||||
+ ", " + e.getMessage() + "; removing bubble");
|
||||
mBubbleController.removeBubble(mBubble.getKey(),
|
||||
mBubbleController.removeBubble(getBubbleKey(),
|
||||
BubbleController.DISMISS_INVALID_INTENT);
|
||||
}
|
||||
});
|
||||
@@ -241,6 +244,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
|
||||
|
||||
mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
|
||||
true /* singleTaskInstance */);
|
||||
|
||||
// Set ActivityView's alpha value as zero, since there is no view content to be shown.
|
||||
setContentVisibility(false);
|
||||
addView(mActivityView);
|
||||
@@ -342,6 +346,15 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
|
||||
mStackView = stackView;
|
||||
}
|
||||
|
||||
public void setOverflow(boolean overflow) {
|
||||
mIsOverflow = overflow;
|
||||
|
||||
Intent target = new Intent(mContext, BubbleOverflowActivity.class);
|
||||
mPendingIntent = PendingIntent.getActivity(mContext, /* requestCode */ 0,
|
||||
target, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
mSettingsIcon.setVisibility(GONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bubble used to populate this view.
|
||||
*/
|
||||
@@ -350,14 +363,14 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
|
||||
Log.d(TAG, "update: bubble=" + (bubble != null ? bubble.getKey() : "null"));
|
||||
}
|
||||
boolean isNew = mBubble == null;
|
||||
if (isNew || bubble.getKey().equals(mBubble.getKey())) {
|
||||
if (isNew || bubble != null && bubble.getKey().equals(mBubble.getKey())) {
|
||||
mBubble = bubble;
|
||||
mSettingsIcon.setContentDescription(getResources().getString(
|
||||
R.string.bubbles_settings_button_description, bubble.getAppName()));
|
||||
|
||||
if (isNew) {
|
||||
mBubbleIntent = mBubble.getBubbleIntent();
|
||||
if (mBubbleIntent != null || mBubble.getShortcutInfo() != null) {
|
||||
mPendingIntent = mBubble.getBubbleIntent();
|
||||
if (mPendingIntent != null || mBubble.getShortcutInfo() != null) {
|
||||
setContentVisibility(false);
|
||||
mActivityView.setVisibility(VISIBLE);
|
||||
}
|
||||
@@ -393,12 +406,16 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO(138116789) Fix overflow height.
|
||||
void updateHeight() {
|
||||
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
|
||||
Log.d(TAG, "updateHeight: bubble=" + getBubbleKey());
|
||||
}
|
||||
if (usingActivityView()) {
|
||||
float desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight);
|
||||
float desiredHeight = mMinHeight;
|
||||
if (!mIsOverflow) {
|
||||
desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight);
|
||||
}
|
||||
float height = Math.min(desiredHeight, getMaxExpandedHeight());
|
||||
height = Math.max(height, mMinHeight);
|
||||
LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
|
||||
@@ -423,8 +440,12 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
|
||||
int bottomInset = getRootWindowInsets() != null
|
||||
? getRootWindowInsets().getStableInsetBottom()
|
||||
: 0;
|
||||
return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight
|
||||
int mh = mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight
|
||||
- mPointerMargin - bottomInset;
|
||||
Log.i(TAG, "max exp height: " + mh);
|
||||
// return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight
|
||||
// - mPointerMargin - bottomInset;
|
||||
return mh;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -543,7 +564,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
|
||||
}
|
||||
|
||||
private boolean usingActivityView() {
|
||||
return (mBubbleIntent != null || mBubble.getShortcutInfo() != null)
|
||||
return (mPendingIntent != null || mBubble.getShortcutInfo() != null)
|
||||
&& mActivityView != null;
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,9 @@ public class BubbleExperimentConfig {
|
||||
private static final String ALLOW_BUBBLE_MENU = "allow_bubble_screenshot_menu";
|
||||
private static final boolean ALLOW_BUBBLE_MENU_DEFAULT = false;
|
||||
|
||||
private static final String ALLOW_BUBBLE_OVERFLOW = "allow_bubble_overflow";
|
||||
private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = false;
|
||||
|
||||
/**
|
||||
* When true, if a notification has the information necessary to bubble (i.e. valid
|
||||
* contentIntent and an icon or image), then a {@link android.app.Notification.BubbleMetadata}
|
||||
@@ -140,6 +143,16 @@ public class BubbleExperimentConfig {
|
||||
ALLOW_BUBBLE_MENU_DEFAULT ? 1 : 0) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* When true, show a menu when a bubble is long-pressed, which will allow the user to take
|
||||
* actions on that bubble.
|
||||
*/
|
||||
static boolean allowBubbleOverflow(Context context) {
|
||||
return Settings.Secure.getInt(context.getContentResolver(),
|
||||
ALLOW_BUBBLE_OVERFLOW,
|
||||
ALLOW_BUBBLE_OVERFLOW_DEFAULT ? 1 : 0) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* If {@link #allowAnyNotifToBubble(Context)} is true, this method creates and adds
|
||||
* {@link android.app.Notification.BubbleMetadata} to the notification entry as long as
|
||||
|
||||
@@ -1,15 +1,42 @@
|
||||
/*
|
||||
* 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.DEBUG_OVERFLOW;
|
||||
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
|
||||
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.recyclerview.widget.GridLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.systemui.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
@@ -17,9 +44,13 @@ import javax.inject.Inject;
|
||||
* Must be public to be accessible to androidx...AppComponentFactory
|
||||
*/
|
||||
public class BubbleOverflowActivity extends Activity {
|
||||
private RecyclerView mRecyclerView;
|
||||
private int mMaxBubbles;
|
||||
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES;
|
||||
|
||||
private BubbleController mBubbleController;
|
||||
private BubbleOverflowAdapter mAdapter;
|
||||
private RecyclerView mRecyclerView;
|
||||
private List<Bubble> mOverflowBubbles = new ArrayList<>();
|
||||
private int mMaxBubbles;
|
||||
|
||||
@Inject
|
||||
public BubbleOverflowActivity(BubbleController controller) {
|
||||
@@ -35,17 +66,42 @@ public class BubbleOverflowActivity extends Activity {
|
||||
mMaxBubbles = getResources().getInteger(R.integer.bubbles_max_rendered);
|
||||
mRecyclerView = findViewById(R.id.bubble_overflow_recycler);
|
||||
mRecyclerView.setLayoutManager(
|
||||
new GridLayoutManager(getApplicationContext(), /* numberOfColumns */ mMaxBubbles));
|
||||
new GridLayoutManager(getApplicationContext(),
|
||||
getResources().getInteger(R.integer.bubbles_overflow_columns)));
|
||||
|
||||
mAdapter = new BubbleOverflowAdapter(mOverflowBubbles,
|
||||
mBubbleController::promoteBubbleFromOverflow);
|
||||
mRecyclerView.setAdapter(mAdapter);
|
||||
|
||||
updateData(mBubbleController.getOverflowBubbles());
|
||||
mBubbleController.setOverflowCallback(() -> {
|
||||
updateData(mBubbleController.getOverflowBubbles());
|
||||
});
|
||||
}
|
||||
|
||||
void setBackgroundColor() {
|
||||
final TypedArray ta = getApplicationContext().obtainStyledAttributes(
|
||||
new int[] {android.R.attr.colorBackgroundFloating});
|
||||
new int[]{android.R.attr.colorBackgroundFloating});
|
||||
int bgColor = ta.getColor(0, Color.WHITE);
|
||||
ta.recycle();
|
||||
findViewById(android.R.id.content).setBackgroundColor(bgColor);
|
||||
}
|
||||
|
||||
void updateData(List<Bubble> bubbles) {
|
||||
mOverflowBubbles.clear();
|
||||
if (bubbles.size() > mMaxBubbles) {
|
||||
mOverflowBubbles.addAll(bubbles.subList(mMaxBubbles, bubbles.size()));
|
||||
} else {
|
||||
mOverflowBubbles.addAll(bubbles);
|
||||
}
|
||||
mAdapter.notifyDataSetChanged();
|
||||
|
||||
if (DEBUG_OVERFLOW) {
|
||||
Log.d(TAG, "Updated overflow bubbles:\n" + BubbleDebugConfig.formatBubblesString(
|
||||
mOverflowBubbles, /*selected*/ null));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
@@ -75,3 +131,48 @@ public class BubbleOverflowActivity extends Activity {
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
class BubbleOverflowAdapter extends RecyclerView.Adapter<BubbleOverflowAdapter.ViewHolder> {
|
||||
private Consumer<Bubble> mPromoteBubbleFromOverflow;
|
||||
private List<Bubble> mBubbles;
|
||||
|
||||
public BubbleOverflowAdapter(List<Bubble> list, Consumer<Bubble> promoteBubble) {
|
||||
mBubbles = list;
|
||||
mPromoteBubbleFromOverflow = promoteBubble;
|
||||
}
|
||||
|
||||
@Override
|
||||
public BubbleOverflowAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
|
||||
int viewType) {
|
||||
BadgedImageView view = (BadgedImageView) LayoutInflater.from(parent.getContext())
|
||||
.inflate(R.layout.bubble_view, parent, false);
|
||||
view.setPadding(15, 15, 15, 15);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(ViewHolder vh, int index) {
|
||||
Bubble bubble = mBubbles.get(index);
|
||||
|
||||
vh.mBadgedImageView.update(bubble);
|
||||
vh.mBadgedImageView.setOnClickListener(view -> {
|
||||
mBubbles.remove(bubble);
|
||||
notifyDataSetChanged();
|
||||
mPromoteBubbleFromOverflow.accept(bubble);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return mBubbles.size();
|
||||
}
|
||||
|
||||
public static class ViewHolder extends RecyclerView.ViewHolder {
|
||||
public BadgedImageView mBadgedImageView;
|
||||
|
||||
public ViewHolder(BadgedImageView v) {
|
||||
super(v);
|
||||
mBadgedImageView = v;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,6 +33,8 @@ import android.app.Notification;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.graphics.Paint;
|
||||
@@ -40,6 +42,9 @@ import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.VibrationEffect;
|
||||
import android.os.Vibrator;
|
||||
@@ -58,6 +63,7 @@ import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
|
||||
import android.view.animation.AccelerateDecelerateInterpolator;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -195,8 +201,6 @@ public class BubbleStackView extends FrameLayout {
|
||||
private int mPointerHeight;
|
||||
private int mStatusBarHeight;
|
||||
private int mImeOffset;
|
||||
private int mBubbleMenuOffset = 252;
|
||||
private BubbleIconFactory mBubbleIconFactory;
|
||||
private Bubble mExpandedBubble;
|
||||
private boolean mIsExpanded;
|
||||
|
||||
@@ -320,6 +324,8 @@ public class BubbleStackView extends FrameLayout {
|
||||
private Runnable mAfterMagnet;
|
||||
|
||||
private int mOrientation = Configuration.ORIENTATION_UNDEFINED;
|
||||
private BubbleExpandedView mOverflowExpandedView;
|
||||
private ImageView mOverflowBtn;
|
||||
|
||||
public BubbleStackView(Context context, BubbleData data,
|
||||
@Nullable SurfaceSynchronizer synchronizer) {
|
||||
@@ -369,8 +375,6 @@ public class BubbleStackView extends FrameLayout {
|
||||
mBubbleContainer.setClipChildren(false);
|
||||
addView(mBubbleContainer, new FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT));
|
||||
|
||||
mBubbleIconFactory = new BubbleIconFactory(context);
|
||||
|
||||
mExpandedViewContainer = new FrameLayout(context);
|
||||
mExpandedViewContainer.setElevation(elevation);
|
||||
mExpandedViewContainer.setPadding(mExpandedViewPadding, mExpandedViewPadding,
|
||||
@@ -414,6 +418,10 @@ public class BubbleStackView extends FrameLayout {
|
||||
setFocusable(true);
|
||||
mBubbleContainer.bringToFront();
|
||||
|
||||
if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
|
||||
setUpOverflow();
|
||||
}
|
||||
|
||||
setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> {
|
||||
if (!mIsExpanded || mIsExpansionAnimating) {
|
||||
return view.onApplyWindowInsets(insets);
|
||||
@@ -421,7 +429,13 @@ public class BubbleStackView extends FrameLayout {
|
||||
mExpandedAnimationController.updateYPosition(
|
||||
// Update the insets after we're done translating otherwise position
|
||||
// calculation for them won't be correct.
|
||||
() -> mExpandedBubble.getExpandedView().updateInsets(insets));
|
||||
() -> {
|
||||
if (mExpandedBubble == null) {
|
||||
mOverflowExpandedView.updateInsets(insets);
|
||||
} else {
|
||||
mExpandedBubble.getExpandedView().updateInsets(insets);
|
||||
}
|
||||
});
|
||||
return view.onApplyWindowInsets(insets);
|
||||
});
|
||||
|
||||
@@ -433,7 +447,11 @@ public class BubbleStackView extends FrameLayout {
|
||||
// Reposition & adjust the height for new orientation
|
||||
if (mIsExpanded) {
|
||||
mExpandedViewContainer.setTranslationY(getExpandedViewY());
|
||||
mExpandedBubble.getExpandedView().updateView();
|
||||
if (mExpandedBubble == null) {
|
||||
mOverflowExpandedView.updateView();
|
||||
} else {
|
||||
mExpandedBubble.getExpandedView().updateView();
|
||||
}
|
||||
}
|
||||
|
||||
// Need to update the padding around the view
|
||||
@@ -499,6 +517,51 @@ public class BubbleStackView extends FrameLayout {
|
||||
mBubbleMenuView = findViewById(R.id.bubble_menu_container);
|
||||
}
|
||||
|
||||
private void setUpOverflow() {
|
||||
mOverflowExpandedView = (BubbleExpandedView) mInflater.inflate(
|
||||
R.layout.bubble_expanded_view, this /* root */, false /* attachToRoot */);
|
||||
mOverflowExpandedView.setOverflow(true);
|
||||
|
||||
mInflater.inflate(R.layout.bubble_overflow_button, this);
|
||||
mOverflowBtn = findViewById(R.id.bubble_overflow_button);
|
||||
mOverflowBtn.setOnClickListener(v -> {
|
||||
showOverflow();
|
||||
});
|
||||
|
||||
TypedArray ta = mContext.obtainStyledAttributes(
|
||||
new int[]{android.R.attr.colorBackgroundFloating});
|
||||
int bgColor = ta.getColor(0, Color.WHITE /* default */);
|
||||
ta.recycle();
|
||||
|
||||
InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(), 28);
|
||||
ColorDrawable bg = new ColorDrawable(bgColor);
|
||||
AdaptiveIconDrawable adaptiveIcon = new AdaptiveIconDrawable(bg, fg);
|
||||
mOverflowBtn.setImageDrawable(adaptiveIcon);
|
||||
mOverflowBtn.setVisibility(GONE);
|
||||
}
|
||||
|
||||
void showOverflow() {
|
||||
if (DEBUG_BUBBLE_STACK_VIEW) {
|
||||
Log.d(TAG, "Show overflow.");
|
||||
}
|
||||
mExpandedViewContainer.setAlpha(0.0f);
|
||||
mSurfaceSynchronizer.syncSurfaceAndRun(() -> {
|
||||
if (mExpandedBubble != null) {
|
||||
mExpandedBubble.setContentVisibility(false);
|
||||
mExpandedBubble = null;
|
||||
}
|
||||
mExpandedViewContainer.removeAllViews();
|
||||
if (mIsExpanded) {
|
||||
mExpandedViewContainer.addView(mOverflowExpandedView);
|
||||
mOverflowExpandedView.populateExpandedView();
|
||||
mExpandedViewContainer.setVisibility(VISIBLE);
|
||||
mExpandedViewContainer.setAlpha(1.0f);
|
||||
mOverflowExpandedView.setContentVisibility(true);
|
||||
}
|
||||
requestUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
private void setUpFlyout() {
|
||||
if (mFlyout != null) {
|
||||
removeView(mFlyout);
|
||||
@@ -734,9 +797,10 @@ public class BubbleStackView extends FrameLayout {
|
||||
new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
|
||||
ViewClippingUtil.setClippingDeactivated(bubble.getIconView(), true, mClippingParameters);
|
||||
animateInFlyoutForBubble(bubble);
|
||||
updatePointerPosition();
|
||||
updateOverflowBtnVisibility( /*apply */ true);
|
||||
requestUpdate();
|
||||
logBubbleEvent(bubble, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__POSTED);
|
||||
updatePointerPosition();
|
||||
}
|
||||
|
||||
// via BubbleData.Listener
|
||||
@@ -753,7 +817,32 @@ public class BubbleStackView extends FrameLayout {
|
||||
} else {
|
||||
Log.d(TAG, "was asked to remove Bubble, but didn't find the view! " + bubble);
|
||||
}
|
||||
updatePointerPosition();
|
||||
updateOverflowBtnVisibility(/* apply */ true);
|
||||
}
|
||||
|
||||
private void updateOverflowBtnVisibility(boolean apply) {
|
||||
if (!BubbleExperimentConfig.allowBubbleOverflow(mContext)) {
|
||||
return;
|
||||
}
|
||||
if (mIsExpanded) {
|
||||
if (DEBUG_BUBBLE_STACK_VIEW) {
|
||||
Log.d(TAG, "Expanded && overflow > 0. Show overflow button at");
|
||||
Log.d(TAG, "x: " + mExpandedAnimationController.getOverflowBtnLeft());
|
||||
Log.d(TAG, "y: " + mExpandedAnimationController.getExpandedY());
|
||||
}
|
||||
mOverflowBtn.setX(mExpandedAnimationController.getOverflowBtnLeft());
|
||||
mOverflowBtn.setY(mExpandedAnimationController.getExpandedY());
|
||||
mOverflowBtn.setVisibility(VISIBLE);
|
||||
mExpandedAnimationController.setShowOverflowBtn(true);
|
||||
if (apply) {
|
||||
mExpandedAnimationController.expandFromStack(null /* after */);
|
||||
}
|
||||
} else {
|
||||
if (DEBUG_BUBBLE_STACK_VIEW) {
|
||||
Log.d(TAG, "Collapsed. Hide overflow button.");
|
||||
}
|
||||
mOverflowBtn.setVisibility(GONE);
|
||||
}
|
||||
}
|
||||
|
||||
// via BubbleData.Listener
|
||||
@@ -953,6 +1042,12 @@ public class BubbleStackView extends FrameLayout {
|
||||
final Bubble previouslySelected = mExpandedBubble;
|
||||
beforeExpandedViewAnimation();
|
||||
|
||||
if (DEBUG_BUBBLE_STACK_VIEW) {
|
||||
Log.d(TAG, "animateCollapse");
|
||||
Log.d(TAG, BubbleDebugConfig.formatBubblesString(this.getBubblesOnScreen(),
|
||||
this.getExpandedBubble()));
|
||||
}
|
||||
updateOverflowBtnVisibility(/* apply */ false);
|
||||
mBubbleContainer.cancelAllAnimations();
|
||||
mExpandedAnimationController.collapseBackToStack(
|
||||
mStackAnimationController.getStackPositionAlongNearestHorizontalEdge()
|
||||
@@ -960,7 +1055,9 @@ public class BubbleStackView extends FrameLayout {
|
||||
() -> {
|
||||
mBubbleContainer.setActiveController(mStackAnimationController);
|
||||
afterExpandedViewAnimation();
|
||||
previouslySelected.setContentVisibility(false);
|
||||
if (previouslySelected != null) {
|
||||
previouslySelected.setContentVisibility(false);
|
||||
}
|
||||
});
|
||||
|
||||
mExpandedViewXAnim.animateToFinalPosition(getCollapsedX());
|
||||
@@ -975,12 +1072,12 @@ public class BubbleStackView extends FrameLayout {
|
||||
beforeExpandedViewAnimation();
|
||||
|
||||
mBubbleContainer.setActiveController(mExpandedAnimationController);
|
||||
updateOverflowBtnVisibility(/* apply */ false);
|
||||
mExpandedAnimationController.expandFromStack(() -> {
|
||||
updatePointerPosition();
|
||||
afterExpandedViewAnimation();
|
||||
} /* after */);
|
||||
|
||||
|
||||
mExpandedViewContainer.setTranslationX(getCollapsedX());
|
||||
mExpandedViewContainer.setTranslationY(getCollapsedY());
|
||||
mExpandedViewContainer.setAlpha(0f);
|
||||
@@ -1063,9 +1160,11 @@ public class BubbleStackView extends FrameLayout {
|
||||
Log.d(TAG, "onDragStart()");
|
||||
}
|
||||
if (mIsExpanded || mIsExpansionAnimating) {
|
||||
if (DEBUG_BUBBLE_STACK_VIEW) {
|
||||
Log.d(TAG, "mIsExpanded or mIsExpansionAnimating");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
hideBubbleMenu();
|
||||
mStackAnimationController.cancelStackPositionAnimations();
|
||||
mBubbleContainer.setActiveController(mStackAnimationController);
|
||||
@@ -1545,7 +1644,11 @@ public class BubbleStackView extends FrameLayout {
|
||||
if (!mExpandedViewYAnim.isRunning()) {
|
||||
// We're not animating so set the value
|
||||
mExpandedViewContainer.setTranslationY(y);
|
||||
mExpandedBubble.getExpandedView().updateView();
|
||||
if (mExpandedBubble == null) {
|
||||
mOverflowExpandedView.updateView();
|
||||
} else {
|
||||
mExpandedBubble.getExpandedView().updateView();
|
||||
}
|
||||
} else {
|
||||
// We are animating so update the value; there is an end listener on the animator
|
||||
// that will ensure expandedeView.updateView gets called.
|
||||
@@ -1571,23 +1674,28 @@ public class BubbleStackView extends FrameLayout {
|
||||
}
|
||||
|
||||
private void updatePointerPosition() {
|
||||
if (DEBUG_BUBBLE_STACK_VIEW) {
|
||||
Log.d(TAG, "updatePointerPosition()");
|
||||
}
|
||||
|
||||
Bubble expandedBubble = getExpandedBubble();
|
||||
if (expandedBubble == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index = getBubbleIndex(expandedBubble);
|
||||
if (index >= mMaxBubbles) {
|
||||
// In between state, where extra bubble will be overflowed, and new bubble added
|
||||
index = 0;
|
||||
}
|
||||
float bubbleLeftFromScreenLeft = mExpandedAnimationController.getBubbleLeft(index);
|
||||
float halfBubble = mBubbleSize / 2f;
|
||||
float bubbleCenter = bubbleLeftFromScreenLeft + halfBubble;
|
||||
// Padding might be adjusted for insets, so get it directly from the view
|
||||
bubbleCenter -= mExpandedViewContainer.getPaddingLeft();
|
||||
|
||||
expandedBubble.getExpandedView().setPointerPosition(bubbleCenter);
|
||||
if (index >= mMaxBubbles) {
|
||||
Bubble first = mBubbleData.getBubbles().get(0);
|
||||
first.getExpandedView().setPointerPosition(bubbleCenter);
|
||||
} else {
|
||||
expandedBubble.getExpandedView().setPointerPosition(bubbleCenter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1680,7 +1788,11 @@ public class BubbleStackView extends FrameLayout {
|
||||
if (!isExpanded()) {
|
||||
return false;
|
||||
}
|
||||
return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
|
||||
if (mExpandedBubble == null) {
|
||||
return mOverflowExpandedView.performBackPressIfNeeded();
|
||||
} else {
|
||||
return mExpandedBubble.getExpandedView().performBackPressIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
/** For debugging only */
|
||||
|
||||
@@ -67,6 +67,8 @@ public class ExpandedAnimationController
|
||||
private float mBubblePaddingTop;
|
||||
/** Size of each bubble. */
|
||||
private float mBubbleSizePx;
|
||||
/** Width of the overflow button. */
|
||||
private float mOverflowBtnWidth;
|
||||
/** Height of the status bar. */
|
||||
private float mStatusBarHeight;
|
||||
/** Size of display. */
|
||||
@@ -81,7 +83,7 @@ public class ExpandedAnimationController
|
||||
|
||||
private boolean mAnimatingExpand = false;
|
||||
private boolean mAnimatingCollapse = false;
|
||||
private Runnable mAfterExpand;
|
||||
private @Nullable Runnable mAfterExpand;
|
||||
private Runnable mAfterCollapse;
|
||||
private PointF mCollapsePoint;
|
||||
|
||||
@@ -97,6 +99,7 @@ public class ExpandedAnimationController
|
||||
private boolean mSpringingBubbleToTouch = false;
|
||||
|
||||
private int mExpandedViewPadding;
|
||||
private boolean mShowOverflowBtn;
|
||||
|
||||
public ExpandedAnimationController(Point displaySize, int expandedViewPadding,
|
||||
int orientation) {
|
||||
@@ -116,7 +119,7 @@ public class ExpandedAnimationController
|
||||
/**
|
||||
* Animates expanding the bubbles into a row along the top of the screen.
|
||||
*/
|
||||
public void expandFromStack(Runnable after) {
|
||||
public void expandFromStack(@Nullable Runnable after) {
|
||||
mAnimatingCollapse = false;
|
||||
mAnimatingExpand = true;
|
||||
mAfterExpand = after;
|
||||
@@ -150,6 +153,14 @@ public class ExpandedAnimationController
|
||||
}
|
||||
}
|
||||
|
||||
public void setShowOverflowBtn(boolean showBtn) {
|
||||
mShowOverflowBtn = showBtn;
|
||||
}
|
||||
|
||||
public boolean getShowOverflowBtn() {
|
||||
return mShowOverflowBtn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the bubbles along a curved path, either to expand them along the top or collapse
|
||||
* them back into a stack.
|
||||
@@ -380,6 +391,7 @@ public class ExpandedAnimationController
|
||||
mStackOffsetPx = res.getDimensionPixelSize(R.dimen.bubble_stack_offset);
|
||||
mBubblePaddingTop = res.getDimensionPixelSize(R.dimen.bubble_padding_top);
|
||||
mBubbleSizePx = res.getDimensionPixelSize(R.dimen.individual_bubble_size);
|
||||
mOverflowBtnWidth = mBubbleSizePx;
|
||||
mStatusBarHeight =
|
||||
res.getDimensionPixelSize(com.android.internal.R.dimen.status_bar_height);
|
||||
mBubblesMaxRendered = res.getInteger(R.integer.bubbles_max_rendered);
|
||||
@@ -498,6 +510,14 @@ public class ExpandedAnimationController
|
||||
return getRowLeft() + bubbleFromRowLeft;
|
||||
}
|
||||
|
||||
public float getOverflowBtnLeft() {
|
||||
if (mLayout == null || mLayout.getChildCount() == 0) {
|
||||
return 0;
|
||||
}
|
||||
return getBubbleLeft(mLayout.getChildCount() - 1) + mBubbleSizePx
|
||||
+ getSpaceBetweenBubbles();
|
||||
}
|
||||
|
||||
/**
|
||||
* When expanded, the bubbles are centered in the screen. In portrait, all available space is
|
||||
* used. In landscape we have too much space so the value is restricted. This method accounts
|
||||
@@ -505,7 +525,7 @@ public class ExpandedAnimationController
|
||||
*
|
||||
* @return the desired width to display the expanded bubbles in.
|
||||
*/
|
||||
private float getWidthForDisplayingBubbles() {
|
||||
public float getWidthForDisplayingBubbles() {
|
||||
final float availableWidth = getAvailableScreenWidth(true /* includeStableInsets */);
|
||||
if (mScreenOrientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
// display size y in landscape will be the smaller dimension of the screen
|
||||
@@ -551,7 +571,11 @@ public class ExpandedAnimationController
|
||||
|
||||
final float totalBubbleWidth = bubbleCount * mBubbleSizePx;
|
||||
final float totalGapWidth = (bubbleCount - 1) * getSpaceBetweenBubbles();
|
||||
final float rowWidth = totalGapWidth + totalBubbleWidth;
|
||||
float rowWidth = totalGapWidth + totalBubbleWidth;
|
||||
if (mShowOverflowBtn) {
|
||||
rowWidth += getSpaceBetweenBubbles();
|
||||
rowWidth += mOverflowBtnWidth;
|
||||
}
|
||||
|
||||
// This display size we're using includes the size of the insets, we want the true
|
||||
// center of the display minus the notch here, which means we should include the
|
||||
@@ -559,7 +583,6 @@ public class ExpandedAnimationController
|
||||
final float trueCenter = getAvailableScreenWidth(false /* subtractStableInsets */) / 2f;
|
||||
final float halfRow = rowWidth / 2f;
|
||||
final float rowLeft = trueCenter - halfRow;
|
||||
|
||||
return rowLeft;
|
||||
}
|
||||
|
||||
@@ -567,12 +590,12 @@ public class ExpandedAnimationController
|
||||
* @return Space between bubbles in row above expanded view.
|
||||
*/
|
||||
private float getSpaceBetweenBubbles() {
|
||||
final float rowMargins = mExpandedViewPadding * 2;
|
||||
final float maxRowWidth = getWidthForDisplayingBubbles() - rowMargins;
|
||||
|
||||
final float totalBubbleWidth = mBubblesMaxRendered * mBubbleSizePx;
|
||||
final float totalGapWidth = maxRowWidth - totalBubbleWidth;
|
||||
|
||||
final float rowMargins = mExpandedViewPadding * 2;
|
||||
float totalGapWidth = getWidthForDisplayingBubbles() - rowMargins - totalBubbleWidth;
|
||||
if (mShowOverflowBtn) {
|
||||
totalGapWidth -= mBubbleSizePx;
|
||||
}
|
||||
final int gapCount = mBubblesMaxRendered - 1;
|
||||
final float gapWidth = totalGapWidth / gapCount;
|
||||
return gapWidth;
|
||||
|
||||
@@ -257,7 +257,7 @@ public class BubbleDataTest extends SysuiTestCase {
|
||||
* enforced by expiring the bubble which was least recently updated (lowest timestamp).
|
||||
*/
|
||||
@Test
|
||||
public void test_collapsed_addBubble_atMaxBubbles_expiresOldest() {
|
||||
public void test_collapsed_addBubble_atMaxBubbles_overflowsOldest() {
|
||||
// Setup
|
||||
sendUpdatedEntryAtTime(mEntryA1, 1000);
|
||||
sendUpdatedEntryAtTime(mEntryA2, 2000);
|
||||
@@ -269,7 +269,10 @@ public class BubbleDataTest extends SysuiTestCase {
|
||||
// Test
|
||||
sendUpdatedEntryAtTime(mEntryC1, 6000);
|
||||
verifyUpdateReceived();
|
||||
|
||||
// Verify
|
||||
assertBubbleRemoved(mBubbleA1, BubbleController.DISMISS_AGED);
|
||||
assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(mBubbleA1));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user