Loading packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +38 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -148,12 +150,12 @@ class Bubble { } @Nullable BadgedImageView getIconView() { public BadgedImageView getIconView() { return mIconView; } @Nullable BubbleExpandedView getExpandedView() { public BubbleExpandedView getExpandedView() { return mExpandedView; } Loading Loading @@ -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); } Loading Loading @@ -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) */); } } } packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java 0 → 100644 +107 −0 Original line number Diff line number Diff line /* * 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; } } packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +1 −0 Original line number Diff line number Diff line Loading @@ -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; Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +50 −126 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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) { Loading @@ -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); Loading Loading @@ -407,21 +399,21 @@ 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 { if (mIsExpanded && mExpandedBubble != null) { mExpandedBubble.getExpandedView().updateView(); } } }); setClipChildren(false); 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) -> { Loading @@ -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); } }); Loading @@ -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(); } } Loading Loading @@ -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); } Loading @@ -573,7 +529,7 @@ public class BubbleStackView extends FrameLayout { public void onThemeChanged() { setUpFlyout(); if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) { setOverflowBtnTheme(); mBubbleOverflow.setOverflowBtnTheme(); } } Loading Loading @@ -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 Loading Loading @@ -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(); Loading @@ -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); } } Loading @@ -849,7 +812,7 @@ public class BubbleStackView extends FrameLayout { } void showOverflow() { setSelectedBubble(null); setSelectedBubble(mBubbleOverflow); } /** Loading @@ -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) { Loading @@ -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); } updateExpandedBubble(); updatePointerPosition(); requestUpdate(); logBubbleEvent(previouslySelected, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); logBubbleEvent(bubbleToSelect, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); Loading Loading @@ -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++) { Loading Loading @@ -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) { Loading @@ -1046,11 +1006,7 @@ public class BubbleStackView extends FrameLayout { () -> { mBubbleContainer.setActiveController(mStackAnimationController); afterExpandedViewAnimation(); if (previouslySelected == null) { mOverflowExpandedView.setContentVisibility(false); } else { previouslySelected.setContentVisibility(false); } }); mExpandedViewXAnim.animateToFinalPosition(getCollapsedX()); Loading Loading @@ -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()); } Loading Loading @@ -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); Loading @@ -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 { Loading Loading @@ -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()); } /** Loading Loading @@ -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)); } /** Loading @@ -1770,15 +1698,11 @@ 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(); } } /** For debugging only */ List<Bubble> getBubblesOnScreen() { Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java 0 → 100644 +30 −0 Original line number Diff line number Diff line /* * 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(); } Loading
packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +38 −4 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading Loading @@ -148,12 +150,12 @@ class Bubble { } @Nullable BadgedImageView getIconView() { public BadgedImageView getIconView() { return mIconView; } @Nullable BubbleExpandedView getExpandedView() { public BubbleExpandedView getExpandedView() { return mExpandedView; } Loading Loading @@ -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); } Loading Loading @@ -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) */); } } }
packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflow.java 0 → 100644 +107 −0 Original line number Diff line number Diff line /* * 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; } }
packages/SystemUI/src/com/android/systemui/bubbles/BubbleOverflowActivity.java +1 −0 Original line number Diff line number Diff line Loading @@ -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; Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +50 −126 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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; Loading @@ -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; Loading Loading @@ -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. */ Loading Loading @@ -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) { Loading @@ -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); Loading Loading @@ -407,21 +399,21 @@ 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 { if (mIsExpanded && mExpandedBubble != null) { mExpandedBubble.getExpandedView().updateView(); } } }); setClipChildren(false); 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) -> { Loading @@ -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); } }); Loading @@ -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(); } } Loading Loading @@ -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); } Loading @@ -573,7 +529,7 @@ public class BubbleStackView extends FrameLayout { public void onThemeChanged() { setUpFlyout(); if (BubbleExperimentConfig.allowBubbleOverflow(mContext)) { setOverflowBtnTheme(); mBubbleOverflow.setOverflowBtnTheme(); } } Loading Loading @@ -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 Loading Loading @@ -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(); Loading @@ -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); } } Loading @@ -849,7 +812,7 @@ public class BubbleStackView extends FrameLayout { } void showOverflow() { setSelectedBubble(null); setSelectedBubble(mBubbleOverflow); } /** Loading @@ -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) { Loading @@ -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); } updateExpandedBubble(); updatePointerPosition(); requestUpdate(); logBubbleEvent(previouslySelected, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED); logBubbleEvent(bubbleToSelect, SysUiStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED); Loading Loading @@ -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++) { Loading Loading @@ -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) { Loading @@ -1046,11 +1006,7 @@ public class BubbleStackView extends FrameLayout { () -> { mBubbleContainer.setActiveController(mStackAnimationController); afterExpandedViewAnimation(); if (previouslySelected == null) { mOverflowExpandedView.setContentVisibility(false); } else { previouslySelected.setContentVisibility(false); } }); mExpandedViewXAnim.animateToFinalPosition(getCollapsedX()); Loading Loading @@ -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()); } Loading Loading @@ -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); Loading @@ -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 { Loading Loading @@ -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()); } /** Loading Loading @@ -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)); } /** Loading @@ -1770,15 +1698,11 @@ 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(); } } /** For debugging only */ List<Bubble> getBubblesOnScreen() { Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java 0 → 100644 +30 −0 Original line number Diff line number Diff line /* * 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(); }