Merge "Add the Manage menu!" into rvc-dev
This commit is contained in:
21
packages/SystemUI/res/drawable/bubble_manage_menu_row.xml
Normal file
21
packages/SystemUI/res/drawable/bubble_manage_menu_row.xml
Normal 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>
|
||||
99
packages/SystemUI/res/layout/bubble_manage_menu.xml
Normal file
99
packages/SystemUI/res/layout/bubble_manage_menu.xml
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -605,6 +605,9 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
|
||||
if (mExpandListener != null) {
|
||||
mStackView.setExpandListener(mExpandListener);
|
||||
}
|
||||
|
||||
mStackView.setUnbubbleConversationCallback(notificationEntry ->
|
||||
onUserChangedBubble(notificationEntry, false /* shouldBubble */));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) */);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) */);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user