Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9d60f2f8 authored by TreeHugger Robot's avatar TreeHugger Robot Committed by Android (Google) Code Review
Browse files

Merge "Interface to represent overflow as bubble"

parents 59ecddf8 3cd75d77
Loading
Loading
Loading
Loading
+38 −4
Original line number Diff line number Diff line
@@ -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) */);
        }
    }
}
+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;
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -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;
+50 −126
Original line number Diff line number Diff line
@@ -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,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) -> {
@@ -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);
                }
                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);
                    }
                });

        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,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() {
+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();
}