Merge "Add the Manage menu!" into rvc-dev

This commit is contained in:
Josh Tsuji
2020-04-21 19:43:11 +00:00
committed by Android (Google) Code Review
11 changed files with 389 additions and 107 deletions

View File

@@ -0,0 +1,21 @@
<?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
-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<ripple android:color="#99999999" />
</item>
</selector>

View File

@@ -0,0 +1,99 @@
<?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
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/rounded_bg_full"
android:elevation="@dimen/bubble_manage_menu_elevation"
android:orientation="vertical">
<LinearLayout
android:id="@+id/bubble_manage_menu_dismiss_container"
android:background="@drawable/bubble_manage_menu_row"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_remove_no_shadow"
android:tint="@color/global_actions_text"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault"
android:text="@string/bubble_dismiss_text" />
</LinearLayout>
<LinearLayout
android:id="@+id/bubble_manage_menu_dont_bubble_container"
android:background="@drawable/bubble_manage_menu_row"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_stop_bubble"
android:tint="@color/global_actions_text"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault"
android:text="@string/bubbles_dont_bubble_conversation" />
</LinearLayout>
<LinearLayout
android:id="@+id/bubble_manage_menu_settings_container"
android:background="@drawable/bubble_manage_menu_row"
android:layout_width="match_parent"
android:layout_height="48dp"
android:gravity="center_vertical"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:orientation="horizontal">
<ImageView
android:id="@+id/bubble_manage_menu_settings_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_remove_no_shadow"/>
<TextView
android:id="@+id/bubble_manage_menu_settings_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
</LinearLayout>
</LinearLayout>

View File

@@ -1200,6 +1200,7 @@
snap to the dismiss target. -->
<dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
<dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
<dimen name="bubble_manage_menu_elevation">4dp</dimen>
<dimen name="dismiss_circle_size">52dp</dimen>
<dimen name="dismiss_target_x_size">24dp</dimen>

View File

@@ -2629,6 +2629,8 @@
<string name="bubbles_user_education_manage">Tap Manage to turn off bubbles from this app</string>
<!-- Button text for dismissing the bubble "manage" button tool tip [CHAR LIMIT=20]-->
<string name="bubbles_user_education_got_it">Got it</string>
<!-- Label for the button that takes the user to the notification settings for the given app. -->
<string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string>
<!-- Notification content text when the system navigation mode changes as a result of changing the default launcher [CHAR LIMIT=NONE] -->
<string name="notification_content_system_nav_changed">System navigation updated. To make changes, go to Settings.</string>

View File

@@ -90,6 +90,7 @@ class Bubble implements BubbleViewProvider {
}
private FlyoutMessage mFlyoutMessage;
private Drawable mBadgedAppIcon;
private Bitmap mBadgedImage;
private int mDotColor;
private Path mDotPath;
@@ -133,6 +134,10 @@ class Bubble implements BubbleViewProvider {
return mBadgedImage;
}
public Drawable getBadgedAppIcon() {
return mBadgedAppIcon;
}
@Override
public int getDotColor() {
return mDotColor;
@@ -239,6 +244,7 @@ class Bubble implements BubbleViewProvider {
mAppName = info.appName;
mFlyoutMessage = info.flyoutMessage;
mBadgedAppIcon = info.badgedAppIcon;
mBadgedImage = info.badgedBubbleImage;
mDotColor = info.dotColor;
mDotPath = info.dotPath;

View File

@@ -605,6 +605,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
if (mExpandListener != null) {
mStackView.setExpandListener(mExpandListener);
}
mStackView.setUnbubbleConversationCallback(notificationEntry ->
onUserChangedBubble(notificationEntry, false /* shouldBubble */));
}
}

View File

@@ -43,7 +43,6 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.RemoteException;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@@ -55,14 +54,13 @@ import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.AlphaOptimizedButton;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
/**
* Container for the expanded bubble view, handles rendering the caret and settings icon.
*/
public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
public class BubbleExpandedView extends LinearLayout {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
private enum ActivityViewStatus {
@@ -100,9 +98,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
private int mPointerWidth;
private int mPointerHeight;
private ShapeDrawable mPointerDrawable;
private Rect mTempRect = new Rect();
private int[] mTempLoc = new int[2];
private int mExpandedViewTouchSlop;
@Nullable private Bubble mBubble;
@@ -224,7 +219,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
mExpandedViewTouchSlop = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_slop);
}
@Override
@@ -239,7 +233,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
mPointerDrawable = new ShapeDrawable(TriangleShape.create(
mPointerWidth, mPointerHeight, true /* pointUp */));
mPointerView.setBackground(mPointerDrawable);
@@ -248,7 +241,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
R.dimen.bubble_manage_button_height);
mSettingsIcon = findViewById(R.id.settings_button);
mSettingsIcon.setOnClickListener(this);
mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
true /* singleTaskInstance */);
@@ -289,6 +281,19 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
return mBubble != null ? mBubble.getEntry() : null;
}
void setManageClickListener(OnClickListener manageClickListener) {
findViewById(R.id.settings_button).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.
*/
void updateObscuredTouchableRegion() {
mActivityView.onLocationChanged();
}
void applyThemeAttrs() {
final TypedArray ta = mContext.obtainStyledAttributes(
new int[] {
@@ -472,51 +477,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
- mPointerMargin - bottomInset;
}
/**
* Whether the provided x, y values (in raw coordinates) are in a touchable area of the
* expanded view.
*
* The touchable areas are the ActivityView (plus some slop around it) and the manage button.
*/
boolean intersectingTouchableContent(int rawX, int rawY) {
mTempRect.setEmpty();
if (mActivityView != null) {
mTempLoc = mActivityView.getLocationOnScreen();
mTempRect.set(mTempLoc[0] - mExpandedViewTouchSlop,
mTempLoc[1] - mExpandedViewTouchSlop,
mTempLoc[0] + mActivityView.getWidth() + mExpandedViewTouchSlop,
mTempLoc[1] + mActivityView.getHeight() + mExpandedViewTouchSlop);
}
if (mTempRect.contains(rawX, rawY)) {
return true;
}
mTempLoc = mSettingsIcon.getLocationOnScreen();
mTempRect.set(mTempLoc[0],
mTempLoc[1],
mTempLoc[0] + mSettingsIcon.getWidth(),
mTempLoc[1] + mSettingsIcon.getHeight());
if (mTempRect.contains(rawX, rawY)) {
return true;
}
return false;
}
@Override
public void onClick(View view) {
if (mBubble == null) {
return;
}
int id = view.getId();
if (id == R.id.settings_button) {
Intent intent = mBubble.getSettingsIntent();
mStackView.collapseStack(() -> {
mContext.startActivityAsUser(intent, mBubble.getEntry().getSbn().getUser());
logBubbleClickEvent(mBubble,
SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
});
}
}
/**
* Update appearance of the expanded view being displayed.
*/
@@ -547,10 +507,8 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
* Position of the manage button displayed in the expanded view. Used for placing user
* education about the manage button.
*/
public Rect getManageButtonLocationOnScreen() {
mTempLoc = mSettingsIcon.getLocationOnScreen();
return new Rect(mTempLoc[0], mTempLoc[1], mTempLoc[0] + mSettingsIcon.getWidth(),
mTempLoc[1] + mSettingsIcon.getHeight());
public void getManageButtonBoundsOnScreen(Rect rect) {
mSettingsIcon.getBoundsOnScreen(rect);
}
/**
@@ -611,26 +569,4 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
}
return INVALID_DISPLAY;
}
/**
* Logs bubble UI click event.
*
* @param bubble the bubble notification entry that user is interacting with.
* @param action the user interaction enum.
*/
private void logBubbleClickEvent(Bubble bubble, int action) {
StatusBarNotification notification = bubble.getEntry().getSbn();
SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
notification.getPackageName(),
notification.getNotification().getChannelId(),
notification.getId(),
mStackView.getBubbleIndex(mStackView.getExpandedBubble()),
mStackView.getBubbleCount(),
action,
mStackView.getNormalizedXPosition(),
mStackView.getNormalizedYPosition(),
bubble.showInShade(),
bubble.isOngoing(),
false /* isAppForeground (unused) */);
}
}

View File

@@ -33,12 +33,14 @@ import android.animation.ValueAnimator;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.content.Context;
import android.content.Intent;
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.Outline;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.PointF;
@@ -47,6 +49,7 @@ import android.graphics.RectF;
import android.graphics.Region;
import android.os.Bundle;
import android.os.Vibrator;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.view.Choreographer;
import android.view.DisplayCutout;
@@ -55,6 +58,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
@@ -62,6 +66,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 android.widget.TextView;
import androidx.annotation.MainThread;
@@ -83,6 +88,7 @@ import com.android.systemui.bubbles.animation.StackAnimationController;
import com.android.systemui.model.SysUiState;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
import com.android.systemui.util.DismissCircleView;
import com.android.systemui.util.FloatingContentCoordinator;
@@ -97,6 +103,7 @@ import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
/**
* Renders bubbles in a stack and handles animating expanded and collapsed states.
@@ -224,7 +231,7 @@ public class BubbleStackView extends FrameLayout {
private int mPointerHeight;
private int mStatusBarHeight;
private int mImeOffset;
private BubbleViewProvider mExpandedBubble;
@Nullable private BubbleViewProvider mExpandedBubble;
private boolean mIsExpanded;
/** Whether the stack is currently on the left side of the screen, or animating there. */
@@ -244,6 +251,10 @@ public class BubbleStackView extends FrameLayout {
}
private BubbleController.BubbleExpandListener mExpandListener;
/** Callback to run when we want to unbubble the given notification's conversation. */
private Consumer<NotificationEntry> mUnbubbleConversationCallback;
private SysUiState mSysUiState;
private boolean mViewUpdatedRequested = false;
@@ -255,9 +266,7 @@ public class BubbleStackView extends FrameLayout {
private LayoutInflater mInflater;
// Used for determining view / touch intersection
int[] mTempLoc = new int[2];
RectF mTempRect = new RectF();
private Rect mTempRect = new Rect();
private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
@@ -471,6 +480,11 @@ public class BubbleStackView extends FrameLayout {
return true;
}
// If the manage menu is visible, just hide it.
if (mShowingManage) {
showManageMenu(false /* show */);
}
if (mBubbleData.isExpanded()) {
maybeShowManageEducation(false /* show */);
@@ -627,6 +641,13 @@ public class BubbleStackView extends FrameLayout {
private BubbleManageEducationView mManageEducationView;
private boolean mAnimatingManageEducationAway;
private ViewGroup mManageMenu;
private ImageView mManageSettingsIcon;
private TextView mManageSettingsText;
private boolean mShowingManage = false;
private PhysicsAnimator.SpringConfig mManageSpringConfig = new PhysicsAnimator.SpringConfig(
SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
@SuppressLint("ClickableViewAccessibility")
public BubbleStackView(Context context, BubbleData data,
@Nullable SurfaceSynchronizer synchronizer,
@@ -689,6 +710,8 @@ public class BubbleStackView extends FrameLayout {
mExpandedViewContainer.setClipChildren(false);
addView(mExpandedViewContainer);
setUpManageMenu();
setUpFlyout();
mFlyoutTransitionSpring.setSpring(new SpringForce()
.setStiffness(SpringForce.STIFFNESS_LOW)
@@ -838,7 +861,9 @@ public class BubbleStackView extends FrameLayout {
// ActivityViews, etc.) were touched. Collapse the stack if it's expanded.
setOnTouchListener((view, ev) -> {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
if (mBubbleData.isExpanded()) {
if (mShowingManage) {
showManageMenu(false /* show */);
} else if (mBubbleData.isExpanded()) {
mBubbleData.setExpanded(false);
}
}
@@ -847,6 +872,66 @@ public class BubbleStackView extends FrameLayout {
});
}
private void setUpManageMenu() {
if (mManageMenu != null) {
removeView(mManageMenu);
}
mManageMenu = (ViewGroup) LayoutInflater.from(getContext()).inflate(
R.layout.bubble_manage_menu, this, false);
mManageMenu.setVisibility(View.INVISIBLE);
PhysicsAnimator.getInstance(mManageMenu).setDefaultSpringConfig(mManageSpringConfig);
final TypedArray ta = mContext.obtainStyledAttributes(
new int[] {android.R.attr.dialogCornerRadius});
final int menuCornerRadius = ta.getDimensionPixelSize(0, 0);
ta.recycle();
mManageMenu.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), menuCornerRadius);
}
});
mManageMenu.setClipToOutline(true);
mManageMenu.findViewById(R.id.bubble_manage_menu_dismiss_container).setOnClickListener(
view -> {
showManageMenu(false /* show */);
dismissBubbleIfExists(mBubbleData.getSelectedBubble());
});
mManageMenu.findViewById(R.id.bubble_manage_menu_dont_bubble_container).setOnClickListener(
view -> {
showManageMenu(false /* show */);
final Bubble bubble = mBubbleData.getSelectedBubble();
if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
mUnbubbleConversationCallback.accept(bubble.getEntry());
}
});
mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
view -> {
showManageMenu(false /* show */);
final Bubble bubble = mBubbleData.getSelectedBubble();
if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
final Intent intent = bubble.getSettingsIntent();
collapseStack(() -> {
mContext.startActivityAsUser(
intent, bubble.getEntry().getSbn().getUser());
logBubbleClickEvent(
bubble,
SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
});
}
});
mManageSettingsIcon = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_icon);
mManageSettingsText = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_name);
addView(mManageMenu);
}
private void setUpUserEducation() {
if (mUserEducationView != null) {
removeView(mUserEducationView);
@@ -934,6 +1019,7 @@ public class BubbleStackView extends FrameLayout {
setUpFlyout();
setUpOverflow();
setUpUserEducation();
setUpManageMenu();
}
/** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -960,6 +1046,9 @@ public class BubbleStackView extends FrameLayout {
Math.max(0f, Math.min(1f, mVerticalPosPercentBeforeRotation));
addOnLayoutChangeListener(mOrientationChangedListener);
hideFlyoutImmediate();
mManageMenu.setVisibility(View.INVISIBLE);
mShowingManage = false;
}
@Override
@@ -1100,6 +1189,12 @@ public class BubbleStackView extends FrameLayout {
mExpandListener = listener;
}
/** Sets the function to call to un-bubble the given conversation. */
public void setUnbubbleConversationCallback(
Consumer<NotificationEntry> unbubbleConversationCallback) {
mUnbubbleConversationCallback = unbubbleConversationCallback;
}
/**
* Whether the stack of bubbles is expanded or not.
*/
@@ -1361,15 +1456,14 @@ public class BubbleStackView extends FrameLayout {
mManageEducationView.setAlpha(0);
mManageEducationView.setVisibility(VISIBLE);
mManageEducationView.post(() -> {
final Rect position =
mExpandedBubble.getExpandedView().getManageButtonLocationOnScreen();
mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
final int viewHeight = mManageEducationView.getManageViewHeight();
final int inset = getResources().getDimensionPixelSize(
R.dimen.bubbles_manage_education_top_inset);
mManageEducationView.bringToFront();
mManageEducationView.setManageViewPosition(position.left,
position.top - viewHeight + inset);
mManageEducationView.setPointerPosition(position.centerX() - position.left);
mManageEducationView.setManageViewPosition(mTempRect.left,
mTempRect.top - viewHeight + inset);
mManageEducationView.setPointerPosition(mTempRect.centerX() - mTempRect.left);
mManageEducationView.animate()
.setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION)
.setInterpolator(FAST_OUT_SLOW_IN).alpha(1);
@@ -1443,6 +1537,9 @@ public class BubbleStackView extends FrameLayout {
}
private void animateCollapse() {
// Hide the menu if it's visible.
showManageMenu(false);
mIsExpanded = false;
final BubbleViewProvider previouslySelected = mExpandedBubble;
beforeExpandedViewAnimation();
@@ -1570,9 +1667,9 @@ public class BubbleStackView extends FrameLayout {
*/
@Override
public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
// If the notification shade is expanded, we shouldn't let the ActivityView steal any touch
// events from any location.
if (mNotificationShadeWindowController.getPanelExpanded()) {
// If the notification shade is expanded, or the manage menu is open, we shouldn't let the
// ActivityView steal any touch events from any location.
if (mNotificationShadeWindowController.getPanelExpanded() || mShowingManage) {
touchableRegion.setEmpty();
}
}
@@ -1658,17 +1755,20 @@ public class BubbleStackView extends FrameLayout {
private void dismissMagnetizedObject() {
if (mIsExpanded) {
final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject();
final Bubble draggedOutBubble = mBubbleData.getBubbleWithView(draggedOutBubbleView);
dismissBubbleIfExists(mBubbleData.getBubbleWithView(draggedOutBubbleView));
if (mBubbleData.hasBubbleWithKey(draggedOutBubble.getKey())) {
mBubbleData.notificationEntryRemoved(
draggedOutBubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
}
} else {
mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
}
}
private void dismissBubbleIfExists(@Nullable Bubble bubble) {
if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
mBubbleData.notificationEntryRemoved(
bubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
}
}
/** Prepares and starts the desaturate/darken animation on the bubble stack. */
private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
mDesaturateAndDarkenTargetView = targetView;
@@ -1912,6 +2012,63 @@ public class BubbleStackView extends FrameLayout {
invalidate();
}
private void showManageMenu(boolean show) {
mShowingManage = show;
// This should not happen, since the manage menu is only visible when there's an expanded
// bubble. If we end up in this state, just hide the menu immediately.
if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
mManageMenu.setVisibility(View.INVISIBLE);
return;
}
// If available, update the manage menu's settings option with the expanded bubble's app
// name and icon.
if (show && mBubbleData.hasBubbleWithKey(mExpandedBubble.getKey())) {
final Bubble bubble = mBubbleData.getBubbleWithKey(mExpandedBubble.getKey());
mManageSettingsIcon.setImageDrawable(bubble.getBadgedAppIcon());
mManageSettingsText.setText(getResources().getString(
R.string.bubbles_app_settings, bubble.getAppName()));
}
mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
// When the menu is open, it should be at these coordinates. This will make the menu's
// bottom left corner match up with the button's bottom left corner.
final float targetX = mTempRect.left;
final float targetY = mTempRect.bottom - mManageMenu.getHeight();
if (show) {
mManageMenu.setScaleX(0.5f);
mManageMenu.setScaleY(0.5f);
mManageMenu.setTranslationX(targetX - mManageMenu.getWidth() / 4);
mManageMenu.setTranslationY(targetY + mManageMenu.getHeight() / 4);
mManageMenu.setAlpha(0f);
PhysicsAnimator.getInstance(mManageMenu)
.spring(DynamicAnimation.ALPHA, 1f)
.spring(DynamicAnimation.SCALE_X, 1f)
.spring(DynamicAnimation.SCALE_Y, 1f)
.spring(DynamicAnimation.TRANSLATION_X, targetX)
.spring(DynamicAnimation.TRANSLATION_Y, targetY)
.start();
mManageMenu.setVisibility(View.VISIBLE);
} else {
PhysicsAnimator.getInstance(mManageMenu)
.spring(DynamicAnimation.ALPHA, 0f)
.spring(DynamicAnimation.SCALE_X, 0.5f)
.spring(DynamicAnimation.SCALE_Y, 0.5f)
.spring(DynamicAnimation.TRANSLATION_X, targetX - mManageMenu.getWidth() / 4)
.spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4)
.withEndActions(() -> mManageMenu.setVisibility(View.INVISIBLE))
.start();
}
// Update the AV's obscured touchable region for the new menu visibility state.
mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
}
private void updateExpandedBubble() {
if (DEBUG_BUBBLE_STACK_VIEW) {
Log.d(TAG, "updateExpandedBubble()");
@@ -1921,6 +2078,7 @@ public class BubbleStackView extends FrameLayout {
&& mExpandedBubble.getExpandedView() != null) {
BubbleExpandedView bev = mExpandedBubble.getExpandedView();
mExpandedViewContainer.addView(bev);
bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
bev.populateExpandedView();
mExpandedViewContainer.setVisibility(VISIBLE);
mExpandedViewContainer.setAlpha(1.0f);
@@ -2089,4 +2247,26 @@ public class BubbleStackView extends FrameLayout {
}
return bubbles;
}
/**
* Logs bubble UI click event.
*
* @param bubble the bubble notification entry that user is interacting with.
* @param action the user interaction enum.
*/
private void logBubbleClickEvent(Bubble bubble, int action) {
StatusBarNotification notification = bubble.getEntry().getSbn();
SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
notification.getPackageName(),
notification.getNotification().getChannelId(),
notification.getId(),
getBubbleIndex(getExpandedBubble()),
getBubbleCount(),
action,
getNormalizedXPosition(),
getNormalizedYPosition(),
bubble.showInShade(),
bubble.isOngoing(),
false /* isAppForeground (unused) */);
}
}

View File

@@ -116,6 +116,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
ShortcutInfo shortcutInfo;
String appName;
Bitmap badgedBubbleImage;
Drawable badgedAppIcon;
int dotColor;
Path dotPath;
Bubble.FlyoutMessage flyoutMessage;
@@ -176,6 +177,7 @@ public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask
}
BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon);
info.badgedAppIcon = badgedIcon;
info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable,
badgeBitmapInfo).icon;

View File

@@ -20,15 +20,17 @@ import android.graphics.Bitmap;
import android.graphics.Path;
import android.view.View;
import androidx.annotation.Nullable;
/**
* Interface to represent actual Bubbles and UI elements that act like bubbles, like BubbleOverflow.
*/
interface BubbleViewProvider {
BubbleExpandedView getExpandedView();
@Nullable BubbleExpandedView getExpandedView();
void setContentVisibility(boolean visible);
View getIconView();
@Nullable View getIconView();
void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index);

View File

@@ -61,14 +61,17 @@ internal val animators = WeakHashMap<Any, PhysicsAnimator<*>>()
/**
* Default spring configuration to use for animations where stiffness and/or damping ratio
* were not provided.
* were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig].
*/
private val defaultSpring = PhysicsAnimator.SpringConfig(
private val globalDefaultSpring = PhysicsAnimator.SpringConfig(
SpringForce.STIFFNESS_MEDIUM,
SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
/** Default fling configuration to use for animations where friction was not provided. */
private val defaultFling = PhysicsAnimator.FlingConfig(
/**
* Default fling configuration to use for animations where friction was not provided, and a default
* fling config was not set via [PhysicsAnimator.setDefaultFlingConfig].
*/
private val globalDefaultFling = PhysicsAnimator.FlingConfig(
friction = 1f, min = -Float.MAX_VALUE, max = Float.MAX_VALUE)
/** Whether to log helpful debug information about animations. */
@@ -111,6 +114,12 @@ class PhysicsAnimator<T> private constructor (val target: T) {
/** End actions to run when all animations have completed. */
private val endActions = ArrayList<EndAction>()
/** SpringConfig to use by default for properties whose springs were not provided. */
private var defaultSpring: SpringConfig = globalDefaultSpring
/** FlingConfig to use by default for properties whose fling configs were not provided. */
private var defaultFling: FlingConfig = globalDefaultFling
/**
* Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
* the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
@@ -203,6 +212,19 @@ class PhysicsAnimator<T> private constructor (val target: T) {
return spring(property, toPosition, 0f, config)
}
/**
* Springs a property to a given value using the provided configuration options, and a start
* velocity of 0f.
*
* @see spring
*/
fun spring(
property: FloatPropertyCompat<in T>,
toPosition: Float
): PhysicsAnimator<T> {
return spring(property, toPosition, 0f)
}
/**
* Flings a property using the given start velocity, using a [FlingAnimation] configured using
* the provided configuration settings.
@@ -392,6 +414,14 @@ class PhysicsAnimator<T> private constructor (val target: T) {
return this
}
fun setDefaultSpringConfig(defaultSpring: SpringConfig) {
this.defaultSpring = defaultSpring
}
fun setDefaultFlingConfig(defaultFling: FlingConfig) {
this.defaultFling = defaultFling
}
/** Starts the animations! */
fun start() {
startAction()
@@ -752,7 +782,7 @@ class PhysicsAnimator<T> private constructor (val target: T) {
) {
constructor() :
this(defaultSpring.stiffness, defaultSpring.dampingRatio)
this(globalDefaultSpring.stiffness, globalDefaultSpring.dampingRatio)
constructor(stiffness: Float, dampingRatio: Float) :
this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
@@ -782,10 +812,10 @@ class PhysicsAnimator<T> private constructor (val target: T) {
internal var startVelocity: Float
) {
constructor() : this(defaultFling.friction)
constructor() : this(globalDefaultFling.friction)
constructor(friction: Float) :
this(friction, defaultFling.min, defaultFling.max)
this(friction, globalDefaultFling.min, globalDefaultFling.max)
constructor(friction: Float, min: Float, max: Float) :
this(friction, min, max, startVelocity = 0f)