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

Commit 67c50ee7 authored by Ivan Tkachenko's avatar Ivan Tkachenko
Browse files

Add handle view to bubble bar expanded view

* Added handle view
* Added handle view adaptive color depending on color below

Test: manual, atest BubbleBarHandleViewTest
Flag: WM_BUBBLE_BAR
Bug: 22885379
Change-Id: Ia374b4d66f8db7acc8a035d5983a459817e27bc8
parent 1d4d4431
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -30,6 +30,8 @@
    <color name="bubbles_light">#FFFFFF</color>
    <color name="bubbles_dark">@color/GM2_grey_800</color>
    <color name="bubbles_icon_tint">@color/GM2_grey_700</color>
    <color name="bubble_bar_expanded_view_handle_light">#EBffffff</color>
    <color name="bubble_bar_expanded_view_handle_dark">#99000000</color>

    <!-- PiP -->
    <color name="pip_custom_close_bg">#D93025</color>
+5 −1
Original line number Diff line number Diff line
@@ -229,7 +229,11 @@
    <!-- Size of the bubble bar (height), should match transient_taskbar_size in Launcher. -->
    <dimen name="bubblebar_size">72dp</dimen>
    <!-- The size of the drag handle / menu shown along with a bubble bar expanded view. -->
    <dimen name="bubblebar_expanded_view_menu_size">16dp</dimen>
    <dimen name="bubble_bar_expanded_view_handle_size">40dp</dimen>
    <!-- The width of the drag handle shown along with a bubble bar expanded view. -->
    <dimen name="bubble_bar_expanded_view_handle_width">128dp</dimen>
    <!-- The height of the drag handle shown along with a bubble bar expanded view. -->
    <dimen name="bubble_bar_expanded_view_handle_height">4dp</dimen>

    <!-- Bottom and end margin for compat buttons. -->
    <dimen name="compat_button_margin">24dp</dimen>
+44 −31
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.wm.shell.bubbles.bar;

import android.annotation.ColorInt;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.content.res.TypedArray;
@@ -46,10 +48,10 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
    private BubbleController mController;
    private BubbleTaskViewHelper mBubbleTaskViewHelper;

    private HandleView mMenuView;
    private TaskView mTaskView;
    private BubbleBarHandleView mHandleView = new BubbleBarHandleView(getContext());
    private @Nullable TaskView mTaskView;

    private int mMenuHeight;
    private int mHandleHeight;
    private int mBackgroundColor;
    private float mCornerRadius = 0f;

@@ -83,11 +85,9 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
        super.onFinishInflate();
        Context context = getContext();
        setElevation(getResources().getDimensionPixelSize(R.dimen.bubble_elevation));
        mMenuHeight = context.getResources().getDimensionPixelSize(
                R.dimen.bubblebar_expanded_view_menu_size);
        mMenuView = new HandleView(context);
        addView(mMenuView);

        mHandleHeight = context.getResources().getDimensionPixelSize(
                R.dimen.bubble_bar_expanded_view_handle_size);
        addView(mHandleView);
        applyThemeAttrs();
        setClipToOutline(true);
        setOutlineProvider(new ViewOutlineProvider() {
@@ -123,14 +123,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView

        ta.recycle();

        mMenuView.setCornerRadius(mCornerRadius);
        mMenuHeight = getResources().getDimensionPixelSize(
                R.dimen.bubblebar_expanded_view_menu_size);
        mHandleHeight = getResources().getDimensionPixelSize(
                R.dimen.bubble_bar_expanded_view_handle_size);

        if (mTaskView != null) {
            mTaskView.setCornerRadius(mCornerRadius);
            mTaskView.setElevation(150);
            updateMenuColor();
            updateHandleAndBackgroundColor(true /* animated */);
        }
    }

@@ -138,10 +136,8 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        // Add corner radius here so that the menu extends behind the rounded corners of TaskView.
        int menuViewHeight = Math.min((int) (mMenuHeight + mCornerRadius), height);
        measureChild(mMenuView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
        int menuViewHeight = Math.min(mHandleHeight, height);
        measureChild(mHandleView, widthMeasureSpec, MeasureSpec.makeMeasureSpec(menuViewHeight,
                MeasureSpec.getMode(heightMeasureSpec)));

        if (mTaskView != null) {
@@ -153,12 +149,12 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        // Drag handle above
        final int dragHandleBottom = t + mMenuView.getMeasuredHeight();
        mMenuView.layout(l, t, r, dragHandleBottom);
        final int dragHandleBottom = t + mHandleView.getMeasuredHeight();
        mHandleView.layout(l, t, r, dragHandleBottom);
        if (mTaskView != null) {
            // Subtract radius so that the menu extends behind the rounded corners of TaskView.
            mTaskView.layout(l, (int) (dragHandleBottom - mCornerRadius), r,
            mTaskView.layout(l, dragHandleBottom, r,
                    dragHandleBottom + mTaskView.getMeasuredHeight());
        }
    }
@@ -166,7 +162,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
    @Override
    public void onTaskCreated() {
        setContentVisibility(true);
        updateMenuColor();
        updateHandleAndBackgroundColor(false /* animated */);
    }

    @Override
@@ -218,16 +214,33 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView
        }
    }

    /** Updates the menu bar to be the status bar color specified by the app. */
    private void updateMenuColor() {
    /**
     * Updates the background color to match with task view status/bg color, and sets handle color
     * to contrast with the background
     */
    private void updateHandleAndBackgroundColor(boolean animated) {
        if (mTaskView == null) return;
        ActivityManager.RunningTaskInfo info = mTaskView.getTaskInfo();
        final int taskBgColor = info.taskDescription.getStatusBarColor();
        final int color = Color.valueOf(taskBgColor == -1 ? Color.WHITE : taskBgColor).toArgb();
        if (color != -1) {
            mMenuView.setBackgroundColor(color);
        final int color = getTaskViewColor();
        final boolean isRegionDark = Color.luminance(color) <= 0.5;
        mHandleView.updateHandleColor(isRegionDark, animated);
        setBackgroundColor(color);
    }

    /**
     * Retrieves task view status/nav bar color or background if available
     *
     * TODO (b/283075226): Update with color sampling when
     *                     RegionSamplingHelper or alternative is available
     */
    private @ColorInt int getTaskViewColor() {
        if (mTaskView == null || mTaskView.getTaskInfo() == null) return mBackgroundColor;
        ActivityManager.TaskDescription taskDescription = mTaskView.getTaskInfo().taskDescription;
        if (taskDescription.getStatusBarColor() != Color.TRANSPARENT) {
            return taskDescription.getStatusBarColor();
        } else if (taskDescription.getBackgroundColor() != Color.TRANSPARENT) {
            return taskDescription.getBackgroundColor();
        } else {
            mMenuView.setBackgroundColor(mBackgroundColor);
            return mBackgroundColor;
        }
    }

+115 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.wm.shell.bubbles.bar;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.Nullable;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Outline;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;

import androidx.annotation.ColorInt;
import androidx.core.content.ContextCompat;

import com.android.wm.shell.R;

/**
 * Handle view to show at the top of a bubble bar expanded view.
 */
public class BubbleBarHandleView extends View {
    private static final long COLOR_CHANGE_DURATION = 120;

    private final int mHandleWidth;
    private final int mHandleHeight;
    private final @ColorInt int mHandleLightColor;
    private final @ColorInt int mHandleDarkColor;
    private @Nullable ObjectAnimator mColorChangeAnim;

    public BubbleBarHandleView(Context context) {
        this(context, null);
    }

    public BubbleBarHandleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public BubbleBarHandleView(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        Resources resources = context.getResources();
        mHandleWidth = resources.getDimensionPixelSize(
                R.dimen.bubble_bar_expanded_view_handle_width);
        mHandleHeight = resources.getDimensionPixelSize(
                R.dimen.bubble_bar_expanded_view_handle_height);
        mHandleLightColor = ContextCompat.getColor(context,
                R.color.bubble_bar_expanded_view_handle_light);
        mHandleDarkColor = ContextCompat.getColor(context,
                R.color.bubble_bar_expanded_view_handle_dark);

        setClipToOutline(true);
        setOutlineProvider(new ViewOutlineProvider() {
            @Override
            public void getOutline(View view, Outline outline) {
                final int handleCenterX = view.getWidth() / 2;
                final int handleCenterY = view.getHeight() / 2;
                final float handleRadius = mHandleHeight / 2f;
                Rect handleBounds = new Rect(
                        handleCenterX - mHandleWidth / 2,
                        handleCenterY - mHandleHeight / 2,
                        handleCenterX + mHandleWidth / 2,
                        handleCenterY + mHandleHeight / 2);
                outline.setRoundRect(handleBounds, handleRadius);
            }
        });
    }

    /**
     * Updates the handle color.
     *
     * @param isRegionDark Whether the background behind the handle is dark, and thus the handle
     *                     should be light (and vice versa).
     * @param animated      Whether to animate the change, or apply it immediately.
     */
    public void updateHandleColor(boolean isRegionDark, boolean animated) {
        int newColor = isRegionDark ? mHandleLightColor : mHandleDarkColor;
        if (mColorChangeAnim != null) {
            mColorChangeAnim.cancel();
        }
        if (animated) {
            mColorChangeAnim = ObjectAnimator.ofArgb(this, "backgroundColor", newColor);
            mColorChangeAnim.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    mColorChangeAnim = null;
                }
            });
            mColorChangeAnim.setDuration(COLOR_CHANGE_DURATION);
            mColorChangeAnim.start();
        } else {
            setBackgroundColor(newColor);
        }
    }
}
+0 −42
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 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.wm.shell.bubbles.bar;

import android.content.Context;
import android.view.Gravity;
import android.widget.LinearLayout;

/**
 * Handle / menu view to show at the top of a bubble bar expanded view.
 */
public class HandleView extends LinearLayout {

    // TODO(b/273307221): implement the manage menu in this view.
    public HandleView(Context context) {
        super(context);
        setOrientation(LinearLayout.HORIZONTAL);
        setGravity(Gravity.CENTER);
    }

    /**
     * The menu extends past the top of the TaskView because of the rounded corners. This means
     * to center content in the menu we must subtract the radius (i.e. the amount of space covered
     * by TaskView).
     */
    public void setCornerRadius(float radius) {
        setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(), (int) radius);
    }
}
Loading