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

Commit c9e1e1d3 authored by Lyn Han's avatar Lyn Han
Browse files

Reorder overflow button with kotlin refactor

Add overflow button when overflow is created
Reorder overflow button instead of remove+add
Convert overflow view provider to kotlin

Bug: 161396059
Test: toggle all themes, display size; dismiss single bubble /
entire stack; promote bubble from overflow: no regressions

Change-Id: Iee68c982d1591de4f010aac6c620c7353737bc11
parent 08d37894
Loading
Loading
Loading
Loading
+0 −179
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.Display.INVALID_DISPLAY;
import static android.view.View.GONE;

import static com.android.systemui.bubbles.BadgedImageView.DEFAULT_PATH_SIZE;

import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.drawable.AdaptiveIconDrawable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.InsetDrawable;
import android.util.PathParser;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;

import com.android.systemui.R;

/**
 * Class for showing aged out bubbles.
 */
public class BubbleOverflow implements BubbleViewProvider {
    public static final String KEY = "Overflow";

    private BadgedImageView mOverflowBtn;
    private BubbleExpandedView mExpandedView;
    private LayoutInflater mInflater;
    private Context mContext;
    private Bitmap mIcon;
    private Path mPath;
    private int mBitmapSize;
    private int mIconBitmapSize;
    private int mDotColor;

    public BubbleOverflow(Context context) {
        mContext = context;
        mInflater = LayoutInflater.from(context);
    }

    void setUpOverflow(ViewGroup parentViewGroup, BubbleStackView stackView) {
        updateDimensions();
        mExpandedView = (BubbleExpandedView) mInflater.inflate(
                R.layout.bubble_expanded_view, parentViewGroup /* root */,
                false /* attachToRoot */);
        mExpandedView.setOverflow(true);
        mExpandedView.setStackView(stackView);
        mExpandedView.applyThemeAttrs();
        updateIcon(mContext, parentViewGroup);
    }

    void updateDimensions() {
        mBitmapSize = mContext.getResources().getDimensionPixelSize(R.dimen.bubble_bitmap_size);
        mIconBitmapSize = mContext.getResources().getDimensionPixelSize(
                R.dimen.bubble_overflow_icon_bitmap_size);
        if (mExpandedView != null) {
            mExpandedView.updateDimensions();
        }
    }

    void updateIcon(Context context, ViewGroup parentViewGroup) {
        mContext = context;
        mInflater = LayoutInflater.from(context);
        mOverflowBtn = (BadgedImageView) mInflater.inflate(R.layout.bubble_overflow_button,
                parentViewGroup /* root */,
                false /* attachToRoot */);
        mOverflowBtn.setContentDescription(mContext.getResources().getString(
                R.string.bubble_overflow_button_content_description));
        Resources res = mContext.getResources();

        // Set color for button icon and dot
        TypedValue typedValue = new TypedValue();
        mContext.getTheme().resolveAttribute(android.R.attr.colorAccent, typedValue, true);
        int colorAccent = mContext.getColor(typedValue.resourceId);
        mOverflowBtn.getDrawable().setTint(colorAccent);
        mDotColor = colorAccent;

        // Set color for button and activity background
        ColorDrawable bg = new ColorDrawable(res.getColor(R.color.bubbles_light));
        final int mode = res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
        if (mode == Configuration.UI_MODE_NIGHT_YES) {
            bg = new ColorDrawable(res.getColor(R.color.bubbles_dark));
        }

        // Apply icon inset
        InsetDrawable fg = new InsetDrawable(mOverflowBtn.getDrawable(),
                mBitmapSize - mIconBitmapSize /* inset */);
        AdaptiveIconDrawable adaptiveIconDrawable = new AdaptiveIconDrawable(bg, fg);

        BubbleIconFactory iconFactory = new BubbleIconFactory(mContext);
        mIcon = iconFactory.createBadgedIconBitmap(adaptiveIconDrawable,
                null /* user */,
                true /* shrinkNonAdaptiveIcons */).icon;

        // Get path with dot location
        float scale = iconFactory.getNormalizer().getScale(mOverflowBtn.getDrawable(),
                null /* outBounds */, null /* path */, null /* outMaskShape */);
        float radius = DEFAULT_PATH_SIZE / 2f;
        mPath = PathParser.createPathFromPathData(
                mContext.getResources().getString(com.android.internal.R.string.config_icon_mask));
        Matrix matrix = new Matrix();
        matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
                radius /* pivot y */);
        mPath.transform(matrix);

        mOverflowBtn.setRenderedBubble(this);
    }

    void setVisible(int visible) {
        mOverflowBtn.setVisibility(visible);
    }

    @Override
    public BubbleExpandedView getExpandedView() {
        return mExpandedView;
    }

    @Override
    public int getDotColor() {
        return mDotColor;
    }

    @Override
    public Bitmap getBadgedImage() {
        return mIcon;
    }

    @Override
    public boolean showDot() {
        return false;
    }

    @Override
    public Path getDotPath() {
        return mPath;
    }

    @Override
    public void setContentVisibility(boolean visible) {
        mExpandedView.setContentVisibility(visible);
    }

    @Override
    public View getIconView() {
        return mOverflowBtn;
    }

    @Override
    public String getKey() {
        return BubbleOverflow.KEY;
    }

    @Override
    public int getDisplayId() {
        return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY;
    }
}
+163 −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.content.Context
import android.content.res.Configuration
import android.graphics.Bitmap
import android.graphics.Matrix
import android.graphics.Path
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.InsetDrawable
import android.util.PathParser
import android.util.TypedValue
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import com.android.systemui.R

class BubbleOverflow(
    private val context: Context,
    private val stack: BubbleStackView
) : BubbleViewProvider {

    private var bitmap: Bitmap? = null
    private var dotPath: Path? = null
    private var bitmapSize = 0
    private var iconBitmapSize = 0
    private var dotColor = 0

    private val inflater: LayoutInflater = LayoutInflater.from(context)
    private val expandedView: BubbleExpandedView = inflater
        .inflate(R.layout.bubble_expanded_view, null /* root */, false /* attachToRoot */)
            as BubbleExpandedView
    private val overflowBtn: BadgedImageView = inflater
        .inflate(R.layout.bubble_overflow_button, null /* root */, false /* attachToRoot */)
            as BadgedImageView
    init {
        updateResources()
        with(expandedView) {
            setOverflow(true)
            setStackView(stack)
            applyThemeAttrs()
        }
        with(overflowBtn) {
            setContentDescription(context.resources.getString(
                R.string.bubble_overflow_button_content_description))
            updateBtnTheme()
        }
    }

    fun update() {
        updateResources()
        expandedView.applyThemeAttrs()
        // Apply inset and new style to fresh icon drawable.
        overflowBtn.setImageResource(R.drawable.ic_bubble_overflow_button)
        updateBtnTheme()
    }

    fun updateResources() {
        bitmapSize = context.resources.getDimensionPixelSize(R.dimen.bubble_bitmap_size)
        iconBitmapSize = context.resources.getDimensionPixelSize(
                R.dimen.bubble_overflow_icon_bitmap_size)
        val bubbleSize = context.resources.getDimensionPixelSize(R.dimen.individual_bubble_size)
        overflowBtn.setLayoutParams(FrameLayout.LayoutParams(bubbleSize, bubbleSize))
        expandedView.updateDimensions()
    }

    fun updateBtnTheme() {
        val res = context.resources

        // Set overflow button accent color, dot color
        val typedValue = TypedValue()
        context.theme.resolveAttribute(android.R.attr.colorAccent, typedValue, true)

        val colorAccent = res.getColor(typedValue.resourceId)
        overflowBtn.getDrawable()?.setTint(colorAccent)
        dotColor = colorAccent

        // Set button and activity background color
        val nightMode = (res.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
                == Configuration.UI_MODE_NIGHT_YES)
        val bg = ColorDrawable(res.getColor(
                if (nightMode) R.color.bubbles_dark else R.color.bubbles_light))

        // Set button icon
        val iconFactory = BubbleIconFactory(context)
        val fg = InsetDrawable(overflowBtn.getDrawable(),
                bitmapSize - iconBitmapSize /* inset */)
        bitmap = iconFactory.createBadgedIconBitmap(AdaptiveIconDrawable(bg, fg),
                null /* user */, true /* shrinkNonAdaptiveIcons */).icon

        // Set dot path
        dotPath = PathParser.createPathFromPathData(
                res.getString(com.android.internal.R.string.config_icon_mask))
        val scale = iconFactory.normalizer.getScale(overflowBtn.getDrawable(),
                null /* outBounds */, null /* path */, null /* outMaskShape */)
        val radius = BadgedImageView.DEFAULT_PATH_SIZE / 2f
        val matrix = Matrix()
        matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
                radius /* pivot y */)
        dotPath?.transform(matrix)
        overflowBtn.setRenderedBubble(this)
    }

    fun setVisible(visible: Int) {
        overflowBtn.visibility = visible
    }

    override fun getExpandedView(): BubbleExpandedView? {
        return expandedView
    }

    override fun getDotColor(): Int {
        return dotColor
    }

    override fun getBadgedImage(): Bitmap? {
        return bitmap
    }

    override fun showDot(): Boolean {
        return false
    }

    override fun getDotPath(): Path? {
        return dotPath
    }

    override fun setContentVisibility(visible: Boolean) {
        expandedView.setContentVisibility(visible)
    }

    override fun getIconView(): View? {
        return overflowBtn
    }

    override fun getKey(): String {
        return KEY
    }

    override fun getDisplayId(): Int {
        return expandedView.virtualDisplayId
    }

    companion object {
        @JvmField val KEY = "Overflow"
    }
}
 No newline at end of file
+18 −20
Original line number Diff line number Diff line
@@ -907,7 +907,16 @@ public class BubbleStackView extends FrameLayout
        setFocusable(true);
        mBubbleContainer.bringToFront();

        setUpOverflow();
        mBubbleOverflow = new BubbleOverflow(getContext(), this);
        mBubbleContainer.addView(mBubbleOverflow.getIconView(),
                mBubbleContainer.getChildCount() /* index */,
                new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT));
        updateOverflow();
        mBubbleOverflow.getIconView().setOnClickListener((View v) -> {
            setSelectedBubble(mBubbleOverflow);
            showManageMenu(false);
        });

        mOnImeVisibilityChanged = onImeVisibilityChanged;
        mHideCurrentInputMethodCallback = hideCurrentInputMethodCallback;
@@ -933,7 +942,7 @@ public class BubbleStackView extends FrameLayout
                (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
                    mExpandedAnimationController.updateResources(mOrientation, mDisplaySize);
                    mStackAnimationController.updateResources(mOrientation);
                    mBubbleOverflow.updateDimensions();
                    mBubbleOverflow.updateResources();

                    // Need to update the padding around the view
                    WindowInsets insets = getRootWindowInsets();
@@ -1187,32 +1196,21 @@ public class BubbleStackView extends FrameLayout
        addView(mFlyout, new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
    }

    private void setUpOverflow() {
        int overflowBtnIndex = 0;
        if (mBubbleOverflow == null) {
            mBubbleOverflow = new BubbleOverflow(getContext());
            mBubbleOverflow.setUpOverflow(mBubbleContainer, this);
        } else {
            mBubbleContainer.removeView(mBubbleOverflow.getIconView());
            mBubbleOverflow.setUpOverflow(mBubbleContainer, this);
            overflowBtnIndex = mBubbleContainer.getChildCount();
        }
        mBubbleContainer.addView(mBubbleOverflow.getIconView(), overflowBtnIndex,
                new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
        mBubbleOverflow.getIconView().setOnClickListener(v -> {
            setSelectedBubble(mBubbleOverflow);
            showManageMenu(false);
        });
    private void updateOverflow() {
        mBubbleOverflow.update();
        mBubbleContainer.reorderView(mBubbleOverflow.getIconView(),
                mBubbleContainer.getChildCount() - 1 /* index */);
        updateOverflowVisibility();
    }

    /**
     * Handle theme changes.
     */
    public void onThemeChanged() {
        setUpFlyout();
        setUpOverflow();
        setUpUserEducation();
        setUpManageMenu();
        updateOverflow();
        updateExpandedViewTheme();
    }

@@ -1261,7 +1259,7 @@ public class BubbleStackView extends FrameLayout

    /** Respond to the display size change by recalculating view size and location. */
    public void onDisplaySizeChanged() {
        setUpOverflow();
        updateOverflow();

        WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
        wm.getDefaultDisplay().getRealSize(mDisplaySize);