diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java index 1e39954ed414a..cdeb5c3f25115 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java @@ -38,9 +38,11 @@ import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.UserHandle; import android.provider.Settings; +import android.service.notification.StatusBarNotification; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.shared.system.SysUiStatsLog; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.io.FileDescriptor; @@ -50,7 +52,7 @@ import java.util.Objects; /** * Encapsulates the data and UI elements of a bubble. */ -class Bubble { +class Bubble implements BubbleViewProvider { private static final String TAG = "Bubble"; private NotificationEntry mEntry; @@ -148,12 +150,12 @@ class Bubble { } @Nullable - BadgedImageView getIconView() { + public BadgedImageView getIconView() { return mIconView; } @Nullable - BubbleExpandedView getExpandedView() { + public BubbleExpandedView getExpandedView() { return mExpandedView; } @@ -238,7 +240,7 @@ class Bubble { * Note that this contents visibility doesn't affect visibility at {@link android.view.View}, * and setting {@code false} actually means rendering the expanded view in transparent. */ - void setContentVisibility(boolean visibility) { + public void setContentVisibility(boolean visibility) { if (mExpandedView != null) { mExpandedView.setContentVisibility(visibility); } @@ -481,4 +483,36 @@ class Bubble { public int hashCode() { return Objects.hash(mKey); } + + public void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index) { + if (this.getEntry() == null + || this.getEntry().getSbn() == null) { + SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, + null /* package name */, + null /* notification channel */, + 0 /* notification ID */, + 0 /* bubble position */, + bubbleCount, + action, + normalX, + normalY, + false /* unread bubble */, + false /* on-going bubble */, + false /* isAppForeground (unused) */); + } else { + StatusBarNotification notification = this.getEntry().getSbn(); + SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, + notification.getPackageName(), + notification.getNotification().getChannelId(), + notification.getId(), + index, + bubbleCount, + action, + normalX, + normalY, + this.showInShade(), + this.isOngoing(), + false /* isAppForeground (unused) */); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java new file mode 100644 index 0000000000000..6cf1086e70be5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles; + +import static android.view.View.GONE; +import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.InsetDrawable; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import com.android.systemui.R; + +/** + * Class for showing aged out bubbles. + */ +public class BubbleOverflow implements BubbleViewProvider { + + private ImageView mOverflowBtn; + private BubbleExpandedView mOverflowExpandedView; + private LayoutInflater mInflater; + private Context mContext; + + public BubbleOverflow(Context context) { + mContext = context; + mInflater = LayoutInflater.from(context); + } + + public void setUpOverflow(ViewGroup parentViewGroup) { + mOverflowExpandedView = (BubbleExpandedView) mInflater.inflate( + R.layout.bubble_expanded_view, parentViewGroup /* root */, + false /* attachToRoot */); + mOverflowExpandedView.setOverflow(true); + + mOverflowBtn = (ImageView) mInflater.inflate(R.layout.bubble_overflow_button, + parentViewGroup /* root */, + false /* attachToRoot */); + + setOverflowBtnTheme(); + mOverflowBtn.setVisibility(GONE); + } + + ImageView getBtn() { + return mOverflowBtn; + } + + void setBtnVisible(int visible) { + mOverflowBtn.setVisibility(visible); + } + + // TODO(b/149146374) Propagate theme change to bubbles in overflow. + void setOverflowBtnTheme() { + TypedArray ta = mContext.obtainStyledAttributes( + new int[]{android.R.attr.colorBackgroundFloating}); + int bgColor = ta.getColor(0, Color.WHITE /* default */); + ta.recycle(); + + InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(), 28); + ColorDrawable bg = new ColorDrawable(bgColor); + AdaptiveIconDrawable adaptiveIcon = new AdaptiveIconDrawable(bg, fg); + mOverflowBtn.setImageDrawable(adaptiveIcon); + } + + + public BubbleExpandedView getExpandedView() { + return mOverflowExpandedView; + } + + public void setContentVisibility(boolean visible) { + mOverflowExpandedView.setContentVisibility(visible); + } + + public void logUIEvent(int bubbleCount, int action, float normalX, float normalY, + int index) { + // TODO(b/149133814) Log overflow UI events. + } + + public View getIconView() { + return mOverflowBtn; + } + + public String getKey() { + return BubbleOverflowActivity.KEY; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java index f3cfa834dfc16..756c5981fe382 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java @@ -47,6 +47,7 @@ import javax.inject.Inject; * Must be public to be accessible to androidx...AppComponentFactory */ public class BubbleOverflowActivity extends Activity { + public static final String KEY = "Overflow"; private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleOverflowActivity" : TAG_BUBBLES; private LinearLayout mEmptyState; diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java index cff78cfeaae06..9a6295a80c1c6 100644 --- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java @@ -33,8 +33,6 @@ import android.app.Notification; import android.content.Context; import android.content.res.Configuration; import android.content.res.Resources; -import android.content.res.TypedArray; -import android.graphics.Color; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; import android.graphics.Paint; @@ -42,13 +40,9 @@ import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.InsetDrawable; import android.os.Bundle; import android.os.VibrationEffect; import android.os.Vibrator; -import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.Choreographer; import android.view.DisplayCutout; @@ -63,7 +57,6 @@ import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.animation.AccelerateDecelerateInterpolator; import android.widget.FrameLayout; -import android.widget.ImageView; import androidx.annotation.MainThread; import androidx.annotation.Nullable; @@ -199,7 +192,7 @@ public class BubbleStackView extends FrameLayout { private int mPointerHeight; private int mStatusBarHeight; private int mImeOffset; - private Bubble mExpandedBubble; + private BubbleViewProvider mExpandedBubble; private boolean mIsExpanded; /** Whether the stack is currently on the left side of the screen, or animating there. */ @@ -322,8 +315,8 @@ public class BubbleStackView extends FrameLayout { private Runnable mAfterMagnet; private int mOrientation = Configuration.ORIENTATION_UNDEFINED; - private BubbleExpandedView mOverflowExpandedView; - private ImageView mOverflowBtn; + + private BubbleOverflow mBubbleOverflow; public BubbleStackView(Context context, BubbleData data, @Nullable SurfaceSynchronizer synchronizer) { @@ -333,7 +326,6 @@ public class BubbleStackView extends FrameLayout { mInflater = LayoutInflater.from(context); mTouchHandler = new BubbleTouchHandler(this, data, context); setOnTouchListener(mTouchHandler); - mInflater = LayoutInflater.from(context); Resources res = getResources(); mMaxBubbles = res.getInteger(R.integer.bubbles_max_rendered); @@ -407,12 +399,8 @@ public class BubbleStackView extends FrameLayout { .setStiffness(SpringForce.STIFFNESS_LOW) .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)); mExpandedViewYAnim.addEndListener((anim, cancelled, value, velocity) -> { - if (mIsExpanded) { - if (mExpandedBubble == null) { - mOverflowExpandedView.updateView(); - } else { - mExpandedBubble.getExpandedView().updateView(); - } + if (mIsExpanded && mExpandedBubble != null) { + mExpandedBubble.getExpandedView().updateView(); } }); @@ -420,8 +408,12 @@ public class BubbleStackView extends FrameLayout { setFocusable(true); mBubbleContainer.bringToFront(); + mBubbleOverflow = new BubbleOverflow(mContext); if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) { - setUpOverflow(); + mBubbleOverflow.setUpOverflow(this); + mBubbleContainer.addView(mBubbleOverflow.getBtn(), 0, + new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + } setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> { @@ -432,9 +424,7 @@ public class BubbleStackView extends FrameLayout { // Update the insets after we're done translating otherwise position // calculation for them won't be correct. () -> { - if (mExpandedBubble == null) { - mOverflowExpandedView.updateInsets(insets); - } else { + if (mExpandedBubble != null) { mExpandedBubble.getExpandedView().updateInsets(insets); } }); @@ -449,9 +439,7 @@ public class BubbleStackView extends FrameLayout { // Reposition & adjust the height for new orientation if (mIsExpanded) { mExpandedViewContainer.setTranslationY(getExpandedViewY()); - if (mExpandedBubble == null) { - mOverflowExpandedView.updateView(); - } else { + if (mExpandedBubble != null) { mExpandedBubble.getExpandedView().updateView(); } } @@ -516,40 +504,8 @@ public class BubbleStackView extends FrameLayout { }); } - private void setUpOverflow() { - mOverflowExpandedView = (BubbleExpandedView) mInflater.inflate( - R.layout.bubble_expanded_view, this /* root */, false /* attachToRoot */); - mOverflowExpandedView.setOverflow(true); - - mOverflowBtn = (ImageView) mInflater.inflate(R.layout.bubble_overflow_button, - this /* root */, - false /* attachToRoot */); - - mBubbleContainer.addView(mOverflowBtn, 0, - new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); - - setOverflowBtnTheme(); - mOverflowBtn.setVisibility(GONE); - } - - // TODO(b/149146374) Propagate theme change to bubbles in overflow. - private void setOverflowBtnTheme() { - TypedArray ta = mContext.obtainStyledAttributes( - new int[]{android.R.attr.colorBackgroundFloating}); - int bgColor = ta.getColor(0, Color.WHITE /* default */); - ta.recycle(); - - InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(), 28); - ColorDrawable bg = new ColorDrawable(bgColor); - AdaptiveIconDrawable adaptiveIcon = new AdaptiveIconDrawable(bg, fg); - mOverflowBtn.setImageDrawable(adaptiveIcon); - } - void showExpandedViewContents(int displayId) { - if (mOverflowExpandedView != null - && mOverflowExpandedView.getVirtualDisplayId() == displayId) { - mOverflowExpandedView.setContentVisibility(true); - } else if (mExpandedBubble != null + if (mExpandedBubble != null && mExpandedBubble.getExpandedView().getVirtualDisplayId() == displayId) { mExpandedBubble.setContentVisibility(true); } @@ -573,7 +529,7 @@ public class BubbleStackView extends FrameLayout { public void onThemeChanged() { setUpFlyout(); if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) { - setOverflowBtnTheme(); + mBubbleOverflow.setOverflowBtnTheme(); } } @@ -756,15 +712,22 @@ public class BubbleStackView extends FrameLayout { /** * The {@link BadgedImageView} that is expanded, null if one does not exist. */ - BadgedImageView getExpandedBubbleView() { + View getExpandedBubbleView() { return mExpandedBubble != null ? mExpandedBubble.getIconView() : null; } /** * The {@link Bubble} that is expanded, null if one does not exist. */ + @Nullable Bubble getExpandedBubble() { - return mExpandedBubble; + if (mExpandedBubble == null + || (BubbleExperimentConfig.allowBubbleOverflow(mContext) + && mExpandedBubble.getIconView() == mBubbleOverflow.getBtn() + && mExpandedBubble.getKey() == BubbleOverflowActivity.KEY)) { + return null; + } + return (Bubble) mExpandedBubble; } // via BubbleData.Listener @@ -818,7 +781,7 @@ public class BubbleStackView extends FrameLayout { if (DEBUG_BUBBLE_STACK_VIEW) { Log.d(TAG, "Show overflow button."); } - mOverflowBtn.setVisibility(VISIBLE); + mBubbleOverflow.setBtnVisible(VISIBLE); if (apply) { mExpandedAnimationController.expandFromStack(() -> { updatePointerPosition(); @@ -828,7 +791,7 @@ public class BubbleStackView extends FrameLayout { if (DEBUG_BUBBLE_STACK_VIEW) { Log.d(TAG, "Collapsed. Hide overflow button."); } - mOverflowBtn.setVisibility(GONE); + mBubbleOverflow.setBtnVisible(GONE); } } @@ -849,7 +812,7 @@ public class BubbleStackView extends FrameLayout { } void showOverflow() { - setSelectedBubble(null); + setSelectedBubble(mBubbleOverflow); } /** @@ -858,14 +821,14 @@ public class BubbleStackView extends FrameLayout { * position of any bubble. */ // via BubbleData.Listener - public void setSelectedBubble(@Nullable Bubble bubbleToSelect) { + public void setSelectedBubble(@Nullable BubbleViewProvider bubbleToSelect) { if (DEBUG_BUBBLE_STACK_VIEW) { Log.d(TAG, "setSelectedBubble: " + bubbleToSelect); } if (mExpandedBubble != null && mExpandedBubble.equals(bubbleToSelect)) { return; } - final Bubble previouslySelected = mExpandedBubble; + final BubbleViewProvider previouslySelected = mExpandedBubble; mExpandedBubble = bubbleToSelect; if (mIsExpanded) { @@ -874,14 +837,11 @@ public class BubbleStackView extends FrameLayout { // expanded view becomes visible on the screen. See b/126856255 mExpandedViewContainer.setAlpha(0.0f); mSurfaceSynchronizer.syncSurfaceAndRun(() -> { - if (previouslySelected == null) { - mOverflowExpandedView.setContentVisibility(false); - } else { - previouslySelected.setContentVisibility(false); - } + previouslySelected.setContentVisibility(false); updateExpandedBubble(); updatePointerPosition(); requestUpdate(); + logBubbleEvent(previouslySelected, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); logBubbleEvent(bubbleToSelect, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); @@ -941,8 +901,8 @@ public class BubbleStackView extends FrameLayout { if (mIsExpanded) { if (isIntersecting(mBubbleContainer, x, y)) { if (BubbleExperimentConfig.allowBubbleOverflow(mContext) - && isIntersecting(mOverflowBtn, x, y)) { - return mOverflowBtn; + && isIntersecting(mBubbleOverflow.getBtn(), x, y)) { + return mBubbleOverflow.getBtn(); } // Could be tapping or dragging a bubble while expanded for (int i = 0; i < getBubbleCount(); i++) { @@ -1030,7 +990,7 @@ public class BubbleStackView extends FrameLayout { private void animateCollapse() { mIsExpanded = false; - final Bubble previouslySelected = mExpandedBubble; + final BubbleViewProvider previouslySelected = mExpandedBubble; beforeExpandedViewAnimation(); if (DEBUG_BUBBLE_STACK_VIEW) { @@ -1046,11 +1006,7 @@ public class BubbleStackView extends FrameLayout { () -> { mBubbleContainer.setActiveController(mStackAnimationController); afterExpandedViewAnimation(); - if (previouslySelected == null) { - mOverflowExpandedView.setContentVisibility(false); - } else { - previouslySelected.setContentVisibility(false); - } + previouslySelected.setContentVisibility(false); }); mExpandedViewXAnim.animateToFinalPosition(getCollapsedX()); @@ -1093,7 +1049,7 @@ public class BubbleStackView extends FrameLayout { mExpandedAnimateYDistance); } - private void notifyExpansionChanged(Bubble bubble, boolean expanded) { + private void notifyExpansionChanged(BubbleViewProvider bubble, boolean expanded) { if (mExpandListener != null && bubble != null) { mExpandListener.onBubbleExpandChanged(expanded, bubble.getKey()); } @@ -1613,11 +1569,8 @@ public class BubbleStackView extends FrameLayout { Log.d(TAG, "updateExpandedBubble()"); } mExpandedViewContainer.removeAllViews(); - if (mIsExpanded) { - BubbleExpandedView bev = mOverflowExpandedView; - if (mExpandedBubble != null) { - bev = mExpandedBubble.getExpandedView(); - } + if (mIsExpanded && mExpandedBubble != null) { + BubbleExpandedView bev = mExpandedBubble.getExpandedView(); mExpandedViewContainer.addView(bev); bev.populateExpandedView(); mExpandedViewContainer.setVisibility(VISIBLE); @@ -1636,9 +1589,7 @@ public class BubbleStackView extends FrameLayout { if (!mExpandedViewYAnim.isRunning()) { // We're not animating so set the value mExpandedViewContainer.setTranslationY(y); - if (mExpandedBubble == null) { - mOverflowExpandedView.updateView(); - } else { + if (mExpandedBubble != null) { mExpandedBubble.getExpandedView().updateView(); } } else { @@ -1693,15 +1644,16 @@ public class BubbleStackView extends FrameLayout { /** * Finds the bubble index within the stack. * - * @param bubble the bubble to look up. + * @param provider the bubble view provider with the bubble to look up. * @return the index of the bubble view within the bubble stack. The range of the position * is between 0 and the bubble count minus 1. */ - int getBubbleIndex(@Nullable Bubble bubble) { - if (bubble == null) { + int getBubbleIndex(@Nullable BubbleViewProvider provider) { + if (provider == null || provider.getKey() == BubbleOverflowActivity.KEY) { return 0; } - return mBubbleContainer.indexOfChild(bubble.getIconView()); + Bubble b = (Bubble) provider; + return mBubbleContainer.indexOfChild(b.getIconView()); } /** @@ -1733,36 +1685,12 @@ public class BubbleStackView extends FrameLayout { * the user interaction is not specific to one bubble. * @param action the user interaction enum. */ - private void logBubbleEvent(@Nullable Bubble bubble, int action) { - if (bubble == null || bubble.getEntry() == null - || bubble.getEntry().getSbn() == null) { - SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, - null /* package name */, - null /* notification channel */, - 0 /* notification ID */, - 0 /* bubble position */, - getBubbleCount(), - action, - getNormalizedXPosition(), - getNormalizedYPosition(), - false /* unread bubble */, - false /* on-going bubble */, - false /* isAppForeground (unused) */); - } else { - StatusBarNotification notification = bubble.getEntry().getSbn(); - SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, - notification.getPackageName(), - notification.getNotification().getChannelId(), - notification.getId(), - getBubbleIndex(bubble), - getBubbleCount(), - action, - getNormalizedXPosition(), - getNormalizedYPosition(), - bubble.showInShade(), - bubble.isOngoing(), - false /* isAppForeground (unused) */); + private void logBubbleEvent(@Nullable BubbleViewProvider bubble, int action) { + if (bubble == null) { + return; } + bubble.logUIEvent(getBubbleCount(), action, getNormalizedXPosition(), + getNormalizedYPosition(), getBubbleIndex(bubble)); } /** @@ -1770,14 +1698,10 @@ public class BubbleStackView extends FrameLayout { * a back key down/up event pair is forwarded to the bubble Activity. */ boolean performBackPressIfNeeded() { - if (!isExpanded()) { + if (!isExpanded() || mExpandedBubble == null) { return false; } - if (mExpandedBubble == null) { - return mOverflowExpandedView.performBackPressIfNeeded(); - } else { - return mExpandedBubble.getExpandedView().performBackPressIfNeeded(); - } + return mExpandedBubble.getExpandedView().performBackPressIfNeeded(); } /** For debugging only */ diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java new file mode 100644 index 0000000000000..59fc435222d68 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.bubbles; + +import android.view.View; + +/** + * Interface to represent actual Bubbles and UI elements that act like bubbles, like BubbleOverflow. + */ +interface BubbleViewProvider { + BubbleExpandedView getExpandedView(); + void setContentVisibility(boolean visible); + View getIconView(); + void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index); + String getKey(); +}