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

Commit bc2095c8 authored by Jacqueline Bronger's avatar Jacqueline Bronger
Browse files

Use RecyclerView for TV PiP menu buttons.

- Improved a11y messages: Talkback adds context of buttons (PiP menu)
  when focus switches to the RecyclerView + adds item position (X of X)
- Improved readability without switching between different ScrollViews
- Nicer scroll behavior at start and end than with Space elements
- Also fixes move menu arrows not updating when moving the PiP

Bug: 258652853
Test: manual - with and without Talkback: open PiP menu, enter/move/exit
move menu, expand/collapse PiP with and without orientation change, have
test app add and remove custom actions while the menu is open

Change-Id: Idbe4c2abce148d0fd5921371b1c95f8dafc3da5f
parent b946fe30
Loading
Loading
Loading
Loading
+9 −61
Original line number Diff line number Diff line
@@ -44,67 +44,15 @@
            android:background="@color/tv_pip_menu_dim_layer"
            android:alpha="0"/>

        <ScrollView
            android:id="@+id/tv_pip_menu_scroll"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="none"
            android:visibility="gone"/>

        <HorizontalScrollView
            android:id="@+id/tv_pip_menu_horizontal_scroll"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scrollbars="none">

            <LinearLayout
        <com.android.internal.widget.RecyclerView
            android:id="@+id/tv_pip_menu_action_buttons"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:alpha="0">

                <Space
                    android:layout_width="@dimen/pip_menu_button_wrapper_margin"
                    android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>

                <com.android.wm.shell.common.TvWindowMenuActionButton
                    android:id="@+id/tv_pip_menu_fullscreen_button"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/pip_ic_fullscreen_white"
                    android:text="@string/pip_fullscreen" />

                <com.android.wm.shell.common.TvWindowMenuActionButton
                    android:id="@+id/tv_pip_menu_close_button"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/pip_ic_close_white"
                    android:text="@string/pip_close" />

                <!-- More TvWindowMenuActionButtons may be added here at runtime. -->

                <com.android.wm.shell.common.TvWindowMenuActionButton
                    android:id="@+id/tv_pip_menu_move_button"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/pip_ic_move_white"
                    android:text="@string/pip_move" />

                <com.android.wm.shell.common.TvWindowMenuActionButton
                    android:id="@+id/tv_pip_menu_expand_button"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:src="@drawable/pip_ic_collapse"
                    android:visibility="gone"
                    android:text="@string/pip_collapse" />

                <Space
                    android:layout_width="@dimen/pip_menu_button_wrapper_margin"
                    android:layout_height="@dimen/pip_menu_button_wrapper_margin"/>

            </LinearLayout>
        </HorizontalScrollView>
            android:layout_gravity="center"
            android:padding="@dimen/pip_menu_button_start_end_offset"
            android:clipToPadding="false"
            android:alpha="0"
            android:contentDescription="@string/a11y_pip_menu_entered"/>
    </FrameLayout>

    <!-- Frame around the content, just overlapping the corners to make them round -->
+1 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
    android:layout_width="@dimen/tv_window_menu_button_size"
    android:layout_height="@dimen/tv_window_menu_button_size"
    android:padding="@dimen/tv_window_menu_button_margin"
    android:duplicateParentState="true"
    android:stateListAnimator="@animator/tv_window_menu_action_button_animator"
    android:focusable="true">

+1 −1
Original line number Diff line number Diff line
@@ -28,7 +28,7 @@
    <dimen name="pip_menu_background_corner_radius">6dp</dimen>
    <dimen name="pip_menu_border_width">4dp</dimen>
    <dimen name="pip_menu_outer_space">24dp</dimen>
    <dimen name="pip_menu_button_wrapper_margin">26dp</dimen>
    <dimen name="pip_menu_button_start_end_offset">30dp</dimen>

    <!-- outer space minus border width -->
    <dimen name="pip_menu_outer_space_frame">20dp</dimen>
+21 −34
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package com.android.wm.shell.common;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.os.Handler;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -30,11 +32,11 @@ import com.android.wm.shell.R;
/**
 * A common action button for TV window menu layouts.
 */
public class TvWindowMenuActionButton extends RelativeLayout implements View.OnClickListener {
public class TvWindowMenuActionButton extends RelativeLayout {
    private final ImageView mIconImageView;
    private final View mButtonBackgroundView;
    private final View mButtonView;
    private OnClickListener mOnClickListener;

    private Icon mCurrentIcon;

    public TvWindowMenuActionButton(Context context) {
        this(context, null, 0, 0);
@@ -56,7 +58,6 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
        inflater.inflate(R.layout.tv_window_menu_action_button, this);

        mIconImageView = findViewById(R.id.icon);
        mButtonView = findViewById(R.id.button);
        mButtonBackgroundView = findViewById(R.id.background);

        final int[] values = new int[]{android.R.attr.src, android.R.attr.text};
@@ -71,23 +72,6 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
        typedArray.recycle();
    }

    @Override
    public void setOnClickListener(OnClickListener listener) {
        // We do not want to set an OnClickListener to the TvWindowMenuActionButton itself, but only
        // to the ImageView. So let's "cash" the listener we've been passed here and set a "proxy"
        // listener to the ImageView.
        mOnClickListener = listener;
        mButtonView.setOnClickListener(listener != null ? this : null);
    }

    @Override
    public void onClick(View v) {
        if (mOnClickListener != null) {
            // Pass the correct view - this.
            mOnClickListener.onClick(this);
        }
    }

    /**
     * Sets the drawable for the button with the given drawable.
     */
@@ -104,11 +88,24 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
        }
    }

    public void setImageIconAsync(Icon icon, Handler handler) {
        mCurrentIcon = icon;
        // Remove old image while waiting for the new one to load.
        mIconImageView.setImageDrawable(null);
        icon.loadDrawableAsync(mContext, d -> {
            // The image hasn't been set any other way and the drawable belongs to the most
            // recently set Icon.
            if (mIconImageView.getDrawable() == null && mCurrentIcon == icon) {
                mIconImageView.setImageDrawable(d);
            }
        }, handler);
    }

    /**
     * Sets the text for description the with the given string.
     */
    public void setTextAndDescription(CharSequence text) {
        mButtonView.setContentDescription(text);
        setContentDescription(text);
    }

    /**
@@ -118,16 +115,6 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC
        setTextAndDescription(getContext().getString(resId));
    }

    @Override
    public void setEnabled(boolean enabled) {
        mButtonView.setEnabled(enabled);
    }

    @Override
    public boolean isEnabled() {
        return mButtonView.isEnabled();
    }

    /**
     * Marks this button as a custom close action button.
     * This changes the style of the action button to highlight that this action finishes the
@@ -147,10 +134,10 @@ public class TvWindowMenuActionButton extends RelativeLayout implements View.OnC

    @Override
    public String toString() {
        if (mButtonView.getContentDescription() == null) {
        if (getContentDescription() == null) {
            return TvWindowMenuActionButton.class.getSimpleName();
        }
        return mButtonView.getContentDescription().toString();
        return getContentDescription().toString();
    }

}
+84 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2022 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.pip.tv;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.app.PendingIntent;
import android.os.Handler;

import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.common.TvWindowMenuActionButton;
import com.android.wm.shell.protolog.ShellProtoLogGroup;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

abstract class TvPipAction {

    private static final String TAG = TvPipAction.class.getSimpleName();

    @Retention(RetentionPolicy.SOURCE)
    @IntDef(prefix = {"ACTION_"}, value = {
            ACTION_FULLSCREEN,
            ACTION_CLOSE,
            ACTION_MOVE,
            ACTION_EXPAND_COLLAPSE,
            ACTION_CUSTOM,
            ACTION_CUSTOM_CLOSE
    })
    public @interface ActionType {
    }

    public static final int ACTION_FULLSCREEN = 0;
    public static final int ACTION_CLOSE = 1;
    public static final int ACTION_MOVE = 2;
    public static final int ACTION_EXPAND_COLLAPSE = 3;
    public static final int ACTION_CUSTOM = 4;
    public static final int ACTION_CUSTOM_CLOSE = 5;

    @ActionType
    private final int mActionType;

    TvPipAction(@ActionType int actionType) {
        mActionType = actionType;
    }

    boolean isCloseAction() {
        return mActionType == ACTION_CLOSE || mActionType == ACTION_CUSTOM_CLOSE;
    }

    @ActionType
    int getActionType() {
        return mActionType;
    }

    abstract void populateButton(@NonNull TvWindowMenuActionButton button, Handler mainHandler);

    abstract PendingIntent getPendingIntent();

    void executePendingIntent() {
        if (getPendingIntent() == null) return;
        try {
            getPendingIntent().send();
        } catch (PendingIntent.CanceledException e) {
            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
                    "%s: Failed to send action, %s", TAG, e);
        }
    }

}
Loading