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

Commit 3774ff68 authored by PETER LIANG's avatar PETER LIANG Committed by Android (Google) Code Review
Browse files

Merge changes Ideea8589,Id8fe4fb6

* changes:
  New feature “Text and reading options” for SetupWizard, Wallpaper, and Settings (9/n).
  New feature “Text and reading options” for SetupWizard, Wallpaper, and Settings (8/n).
parents 893c1ee0 88d63ea3
Loading
Loading
Loading
Loading
+72 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?>
<!--
    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.
-->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/seekbar_frame"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="?android:colorBackground"
    android:gravity="center_vertical">

    <FrameLayout
        android:id="@+id/icon_start_frame"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:background="?android:attr/selectableItemBackgroundBorderless"
        android:focusable="true"
        android:visibility="gone">

        <ImageView
            android:id="@+id/icon_start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="start|center_vertical"
            android:adjustViewBounds="true"
            android:focusable="false"
            android:tint="?android:attr/textColorPrimary"
            android:tintMode="src_in" />
    </FrameLayout>

    <SeekBar
        android:id="@*android:id/seekbar"
        style="@android:style/Widget.Material.SeekBar.Discrete"
        android:layout_width="0dp"
        android:layout_height="48dp"
        android:layout_gravity="center_vertical"
        android:layout_weight="1"
        android:paddingEnd="12dp"
        android:paddingStart="0dp" />

    <FrameLayout
        android:id="@+id/icon_end_frame"
        android:layout_width="48dp"
        android:layout_height="48dp"
        android:background="?android:attr/selectableItemBackgroundBorderless"
        android:focusable="true"
        android:visibility="gone">

        <ImageView
            android:id="@+id/icon_end"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end|center_vertical"
            android:adjustViewBounds="true"
            android:focusable="false"
            android:tint="?android:attr/textColorPrimary"
            android:tintMode="src_in" />
    </FrameLayout>
</LinearLayout>
+8 −10
Original line number Diff line number Diff line
@@ -46,21 +46,19 @@
        android:textAlignment="viewStart"
        android:textColor="?android:attr/textColorSecondary" />

    <SeekBar
        android:id="@*android:id/seekbar"
        android:layout_below="@android:id/summary"
        android:layout_gravity="center_vertical"
    <include
        layout="@layout/icon_discrete_slider"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:paddingStart="0dp"
        android:paddingEnd="12dp"
        style="@android:style/Widget.Material.SeekBar.Discrete" />
        android:layout_height="wrap_content"
        android:layout_below="@android:id/summary" />

    <LinearLayout
        android:id="@+id/label_frame"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@*android:id/seekbar"
        android:orientation="horizontal">
        android:layout_below="@id/seekbar_frame"
        android:orientation="horizontal"
        android:visibility="gone">

        <TextView
            android:id="@android:id/text1"
+4 −0
Original line number Diff line number Diff line
@@ -169,6 +169,10 @@
        <attr name="textStart" format="reference" />
        <attr name="textEnd" format="reference" />
        <attr name="tickMark" format="reference" />
        <attr name="iconStart" format="reference" />
        <attr name="iconEnd" format="reference" />
        <attr name="iconStartContentDescription" format="reference" />
        <attr name="iconEndContentDescription" format="reference" />
    </declare-styleable>

    <declare-styleable name="TintDrawable">
+155 −9
Original line number Diff line number Diff line
@@ -21,6 +21,8 @@ import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.TextView;

@@ -28,17 +30,40 @@ import androidx.annotation.Nullable;
import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.PreferenceViewHolder;

import com.android.internal.util.Preconditions;
import com.android.settings.R;

/** A slider preference with left and right labels **/
/**
 * A labeled {@link SeekBarPreference} with left and right text label, icon label, or both.
 *
 * <p>
 * The component provides the attribute usage below.
 * <attr name="textStart" format="reference" />
 * <attr name="textEnd" format="reference" />
 * <attr name="tickMark" format="reference" />
 * <attr name="iconStart" format="reference" />
 * <attr name="iconEnd" format="reference" />
 * <attr name="iconStartContentDescription" format="reference" />
 * <attr name="iconEndContentDescription" format="reference" />
 * </p>
 *
 * <p> If you set the attribute values {@code iconStartContentDescription} or {@code
 * iconEndContentDescription} from XML, you must also set the corresponding attributes {@code
 * iconStart} or {@code iconEnd}, otherwise throws an {@link IllegalArgumentException}.</p>
 */
public class LabeledSeekBarPreference extends SeekBarPreference {

    private final int mTextStartId;
    private final int mTextEndId;
    private final int mTickMarkId;
    private final int mIconStartId;
    private final int mIconEndId;
    private final int mIconStartContentDescriptionId;
    private final int mIconEndContentDescriptionId;
    private OnPreferenceChangeListener mStopListener;
    @Nullable
    private CharSequence mSummary;
    private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener;

    public LabeledSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
            int defStyleRes) {
@@ -49,13 +74,30 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
        final TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
                R.styleable.LabeledSeekBarPreference);
        mTextStartId = styledAttrs.getResourceId(
                R.styleable.LabeledSeekBarPreference_textStart,
                R.string.summary_placeholder);
                R.styleable.LabeledSeekBarPreference_textStart, /* defValue= */ 0);
        mTextEndId = styledAttrs.getResourceId(
                R.styleable.LabeledSeekBarPreference_textEnd,
                R.string.summary_placeholder);
                R.styleable.LabeledSeekBarPreference_textEnd, /* defValue= */ 0);
        mTickMarkId = styledAttrs.getResourceId(
                R.styleable.LabeledSeekBarPreference_tickMark, /* defValue= */ 0);
        mIconStartId = styledAttrs.getResourceId(
                R.styleable.LabeledSeekBarPreference_iconStart, /* defValue= */ 0);
        mIconEndId = styledAttrs.getResourceId(
                R.styleable.LabeledSeekBarPreference_iconEnd, /* defValue= */ 0);

        mIconStartContentDescriptionId = styledAttrs.getResourceId(
                R.styleable.LabeledSeekBarPreference_iconStartContentDescription,
                /* defValue= */ 0);
        Preconditions.checkArgument(!(mIconStartContentDescriptionId != 0 && mIconStartId == 0),
                "The resource of the iconStart attribute may be invalid or not set, "
                        + "you should set the iconStart attribute and have the valid resource.");

        mIconEndContentDescriptionId = styledAttrs.getResourceId(
                R.styleable.LabeledSeekBarPreference_iconEndContentDescription,
                /* defValue= */ 0);
        Preconditions.checkArgument(!(mIconEndContentDescriptionId != 0 && mIconEndId == 0),
                "The resource of the iconEnd attribute may be invalid or not set, "
                        + "you should set the iconEnd attribute and have the valid resource.");

        mSummary = styledAttrs.getText(R.styleable.Preference_android_summary);
        styledAttrs.recycle();
    }
@@ -71,14 +113,22 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
        super.onBindViewHolder(holder);

        final TextView startText = (TextView) holder.findViewById(android.R.id.text1);
        final TextView endText = (TextView) holder.findViewById(android.R.id.text2);
        if (mTextStartId > 0) {
            startText.setText(mTextStartId);
        }

        final TextView endText = (TextView) holder.findViewById(android.R.id.text2);
        if (mTextEndId > 0) {
            endText.setText(mTextEndId);
        }

        final View labelFrame = holder.findViewById(R.id.label_frame);
        final boolean isValidTextResIdExist = mTextStartId > 0 || mTextEndId > 0;
        labelFrame.setVisibility(isValidTextResIdExist ? View.VISIBLE : View.GONE);

        final SeekBar seekBar = (SeekBar) holder.findViewById(com.android.internal.R.id.seekbar);
        if (mTickMarkId != 0) {
            final Drawable tickMark = getContext().getDrawable(mTickMarkId);
            final SeekBar seekBar = (SeekBar) holder.findViewById(
                    com.android.internal.R.id.seekbar);
            seekBar.setTickMark(tickMark);
        }

@@ -90,19 +140,52 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
            summary.setText(null);
            summary.setVisibility(View.GONE);
        }

        final ViewGroup iconStartFrame = (ViewGroup) holder.findViewById(R.id.icon_start_frame);
        final ImageView iconStartView = (ImageView) holder.findViewById(R.id.icon_start);
        updateIconStartIfNeeded(iconStartFrame, iconStartView, seekBar);

        final ViewGroup iconEndFrame = (ViewGroup) holder.findViewById(R.id.icon_end_frame);
        final ImageView iconEndView = (ImageView) holder.findViewById(R.id.icon_end);
        updateIconEndIfNeeded(iconEndFrame, iconEndView, seekBar);
    }

    public void setOnPreferenceChangeStopListener(OnPreferenceChangeListener listener) {
        mStopListener = listener;
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
        super.onStartTrackingTouch(seekBar);

        if (mSeekBarChangeListener != null) {
            mSeekBarChangeListener.onStartTrackingTouch(seekBar);
        }
    }

    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        super.onProgressChanged(seekBar, progress, fromUser);

        if (mSeekBarChangeListener != null) {
            mSeekBarChangeListener.onProgressChanged(seekBar, progress, fromUser);
        }
    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        super.onStopTrackingTouch(seekBar);

        if (mSeekBarChangeListener != null) {
            mSeekBarChangeListener.onStopTrackingTouch(seekBar);
        }

        if (mStopListener != null) {
            mStopListener.onPreferenceChange(this, seekBar.getProgress());
        }

        // Need to update the icon enabled status
        notifyChanged();
    }

    @Override
@@ -123,5 +206,68 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
    public CharSequence getSummary() {
        return mSummary;
    }

    public void setOnSeekBarChangeListener(SeekBar.OnSeekBarChangeListener seekBarChangeListener) {
        mSeekBarChangeListener = seekBarChangeListener;
    }

    private void updateIconStartIfNeeded(ViewGroup iconFrame, ImageView iconStart,
            SeekBar seekBar) {
        if (mIconStartId == 0) {
            return;
        }

        if (iconStart.getDrawable() == null) {
            iconStart.setImageResource(mIconStartId);
        }

        if (mIconStartContentDescriptionId != 0) {
            final String contentDescription =
                    iconFrame.getContext().getString(mIconStartContentDescriptionId);
            iconFrame.setContentDescription(contentDescription);
        }

        iconFrame.setOnClickListener((view) -> {
            final int progress = getProgress();
            if (progress > 0) {
                setProgress(progress - 1);
            }
        });

        iconFrame.setVisibility(View.VISIBLE);
        setIconViewAndFrameEnabled(iconStart, seekBar.getProgress() > 0);
    }

    private void updateIconEndIfNeeded(ViewGroup iconFrame, ImageView iconEnd, SeekBar seekBar) {
        if (mIconEndId == 0) {
            return;
        }

        if (iconEnd.getDrawable() == null) {
            iconEnd.setImageResource(mIconEndId);
        }

        if (mIconEndContentDescriptionId != 0) {
            final String contentDescription =
                    iconFrame.getContext().getString(mIconEndContentDescriptionId);
            iconFrame.setContentDescription(contentDescription);
        }

        iconFrame.setOnClickListener((view) -> {
            final int progress = getProgress();
            if (progress < getMax()) {
                setProgress(progress + 1);
            }
        });

        iconFrame.setVisibility(View.VISIBLE);
        setIconViewAndFrameEnabled(iconEnd, seekBar.getProgress() < seekBar.getMax());
    }

    private static void setIconViewAndFrameEnabled(View iconView, boolean enabled) {
        iconView.setEnabled(enabled);
        final ViewGroup iconFrame = (ViewGroup) iconView.getParent();
        iconFrame.setEnabled(enabled);
    }
}
+146 −4
Original line number Diff line number Diff line
@@ -18,12 +18,17 @@ package com.android.settings.gestures;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.SeekBar;
import android.widget.TextView;
@@ -31,42 +36,61 @@ import android.widget.TextView;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;

import com.android.internal.R;
import com.android.settings.R;
import com.android.settings.testutils.shadow.ShadowUserManager;
import com.android.settings.widget.LabeledSeekBarPreference;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

/**
 * Tests for {@link LabeledSeekBarPreference}.
 */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {
        ShadowUserManager.class
})
public class LabeledSeekBarPreferenceTest {

    private Context mContext;
    private PreferenceViewHolder mViewHolder;
    private SeekBar mSeekBar;
    private TextView mSummary;
    private ViewGroup mIconStartFrame;
    private ViewGroup mIconEndFrame;
    private View mLabelFrame;
    private LabeledSeekBarPreference mSeekBarPreference;

    @Mock
    private Preference.OnPreferenceChangeListener mListener;

    @Mock
    private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);

        mContext = RuntimeEnvironment.application;
        mContext = Mockito.spy(RuntimeEnvironment.application);
        mSeekBarPreference = new LabeledSeekBarPreference(mContext, null);
        LayoutInflater inflater = LayoutInflater.from(mContext);
        final View view =
                inflater.inflate(mSeekBarPreference.getLayoutResource(),
                        new LinearLayout(mContext), false);
        mViewHolder = PreferenceViewHolder.createInstanceForTests(view);
        mSeekBar = (SeekBar) mViewHolder.findViewById(R.id.seekbar);
        mSummary = (TextView) mViewHolder.findViewById(R.id.summary);
        mSeekBar = (SeekBar) mViewHolder.findViewById(com.android.internal.R.id.seekbar);
        mSummary = (TextView) mViewHolder.findViewById(android.R.id.summary);
        mIconStartFrame = (ViewGroup) mViewHolder.findViewById(R.id.icon_start_frame);
        mIconEndFrame = (ViewGroup) mViewHolder.findViewById(R.id.icon_end_frame);
        mLabelFrame = mViewHolder.findViewById(R.id.label_frame);
    }

    @Test
@@ -97,4 +121,122 @@ public class LabeledSeekBarPreferenceTest {
        assertThat(mSummary.getText()).isEqualTo("");
        assertThat(mSummary.getVisibility()).isEqualTo(View.GONE);
    }

    @Test
    public void setTextAttributes_textStart_textEnd_labelFrameVisible() {
        final AttributeSet attributeSet = Robolectric.buildAttributeSet()
                .addAttribute(R.attr.textStart, "@string/screen_zoom_make_smaller_desc")
                .addAttribute(R.attr.textEnd, "@string/screen_zoom_make_larger_desc")
                .build();
        final LabeledSeekBarPreference seekBarPreference =
                new LabeledSeekBarPreference(mContext, attributeSet);

        seekBarPreference.onBindViewHolder(mViewHolder);

        assertThat(mLabelFrame.getVisibility()).isEqualTo(View.VISIBLE);
    }

    @Test
    public void notSetTextAttributes_labelFrameGone() {
        final AttributeSet attributeSet = Robolectric.buildAttributeSet()
                .build();
        final LabeledSeekBarPreference seekBarPreference =
                new LabeledSeekBarPreference(mContext, attributeSet);

        seekBarPreference.onBindViewHolder(mViewHolder);

        assertThat(mLabelFrame.getVisibility()).isEqualTo(View.GONE);
    }

    @Test
    public void setIconAttributes_iconVisible() {
        final AttributeSet attributeSet = Robolectric.buildAttributeSet()
                .addAttribute(R.attr.iconStart, "@drawable/ic_remove_24dp")
                .addAttribute(R.attr.iconEnd, "@drawable/ic_add_24dp")
                .build();
        final LabeledSeekBarPreference seekBarPreference =
                new LabeledSeekBarPreference(mContext, attributeSet);

        seekBarPreference.onBindViewHolder(mViewHolder);

        assertThat(mIconStartFrame.getVisibility()).isEqualTo(View.VISIBLE);
        assertThat(mIconEndFrame.getVisibility()).isEqualTo(View.VISIBLE);
    }

    @Test
    public void notSetIconAttributes_iconGone() {
        final AttributeSet attributeSet = Robolectric.buildAttributeSet()
                .build();
        final LabeledSeekBarPreference seekBarPreference =
                new LabeledSeekBarPreference(mContext, attributeSet);

        seekBarPreference.onBindViewHolder(mViewHolder);

        assertThat(mIconStartFrame.getVisibility()).isEqualTo(View.GONE);
        assertThat(mIconEndFrame.getVisibility()).isEqualTo(View.GONE);
    }

    @Test
    public void setSeekBarListener_success() {
        mSeekBarPreference.setOnSeekBarChangeListener(mSeekBarChangeListener);
        mSeekBarPreference.onStartTrackingTouch(mSeekBar);
        mSeekBarPreference.onProgressChanged(mSeekBar, /* progress= */ 0,
                /* fromUser= */ false);
        mSeekBarPreference.onStopTrackingTouch(mSeekBar);

        verify(mSeekBarChangeListener).onStartTrackingTouch(any(SeekBar.class));
        verify(mSeekBarChangeListener).onProgressChanged(any(SeekBar.class), anyInt(),
                anyBoolean());
        verify(mSeekBarChangeListener).onStopTrackingTouch(any(SeekBar.class));
    }

    @Test(expected = IllegalArgumentException.class)
    public void setContentDescriptionWithoutIcon_throwException() {
        final AttributeSet attributeSet = Robolectric.buildAttributeSet()
                .addAttribute(R.attr.iconStartContentDescription,
                        "@string/screen_zoom_make_smaller_desc")
                .addAttribute(R.attr.iconEndContentDescription,
                        "@string/screen_zoom_make_larger_desc")
                .build();

        new LabeledSeekBarPreference(mContext, attributeSet);
    }

    @Test
    public void setContentDescriptionWithIcon_success() {
        final String startDescription =
                mContext.getResources().getString(R.string.screen_zoom_make_smaller_desc);
        final String endDescription =
                mContext.getResources().getString(R.string.screen_zoom_make_larger_desc);
        final AttributeSet attributeSet = Robolectric.buildAttributeSet()
                .addAttribute(R.attr.iconStart, "@drawable/ic_remove_24dp")
                .addAttribute(R.attr.iconEnd, "@drawable/ic_add_24dp")
                .addAttribute(R.attr.iconStartContentDescription,
                        "@string/screen_zoom_make_smaller_desc")
                .addAttribute(R.attr.iconEndContentDescription,
                        "@string/screen_zoom_make_larger_desc")
                .build();
        final LabeledSeekBarPreference seekBarPreference =
                new LabeledSeekBarPreference(mContext, attributeSet);

        seekBarPreference.onBindViewHolder(mViewHolder);

        assertThat(mIconStartFrame.getContentDescription().toString().contentEquals(
                startDescription)).isTrue();
        assertThat(mIconEndFrame.getContentDescription().toString().contentEquals(
                endDescription)).isTrue();
    }

    @Test
    public void notSetContentDescriptionAttributes_noDescription() {
        final AttributeSet attributeSet = Robolectric.buildAttributeSet()
                .build();
        final LabeledSeekBarPreference seekBarPreference =
                new LabeledSeekBarPreference(mContext, attributeSet);

        seekBarPreference.onBindViewHolder(mViewHolder);

        assertThat(mIconStartFrame.getContentDescription()).isNull();
        assertThat(mIconEndFrame.getContentDescription()).isNull();
    }
}