diff options
11 files changed, 389 insertions, 107 deletions
diff --git a/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml b/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml new file mode 100644 index 000000000000..a793680a037d --- /dev/null +++ b/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml @@ -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>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/bubble_manage_menu.xml b/packages/SystemUI/res/layout/bubble_manage_menu.xml new file mode 100644 index 000000000000..129282dae77f --- /dev/null +++ b/packages/SystemUI/res/layout/bubble_manage_menu.xml @@ -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>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 7cefa0c498d5..179f8b8ea9f4 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -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> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index cb20e7a15424..d639ed074240 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -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> diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 71f2bc09b983..38bfffbb75d9 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java index c6883c89bd21..e488cf271fdf 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java @@ -605,6 +605,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } + + mStackView.setUnbubbleConversationCallback(notificationEntry -> + onUserChangedBubble(notificationEntry, false /* shouldBubble */)); } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java index 3524696dbc79..bb2365559f74 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java @@ -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[] { @@ -473,51 +478,6 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList } /** - * 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. */ public void updateView() { @@ -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) */); - } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index 0aabdff96e1e..c9069316028c 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -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) */); + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java index c96f9a470ca4..8a57a735f6cb 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java @@ -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; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java index ef84c73b3145..ca3e2e27fa9a 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java @@ -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); diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt index 8625d63a3c7e..db08d64acc10 100644 --- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt +++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt @@ -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 @@ -204,6 +213,19 @@ class PhysicsAnimator<T> private constructor (val target: T) { } /** + * 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) |