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).
This commit is contained in:
PETER LIANG
2022-01-28 00:58:40 +00:00
committed by Android (Google) Code Review
5 changed files with 389 additions and 27 deletions

View File

@@ -0,0 +1,72 @@
<?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>

View File

@@ -46,21 +46,19 @@
android:textAlignment="viewStart" android:textAlignment="viewStart"
android:textColor="?android:attr/textColorSecondary" /> android:textColor="?android:attr/textColorSecondary" />
<SeekBar <include
android:id="@*android:id/seekbar" layout="@layout/icon_discrete_slider"
android:layout_below="@android:id/summary"
android:layout_gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="48dp"
android:paddingStart="0dp"
android:paddingEnd="12dp"
style="@android:style/Widget.Material.SeekBar.Discrete" />
<LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@*android:id/seekbar" android:layout_below="@android:id/summary" />
android:orientation="horizontal">
<LinearLayout
android:id="@+id/label_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/seekbar_frame"
android:orientation="horizontal"
android:visibility="gone">
<TextView <TextView
android:id="@android:id/text1" android:id="@android:id/text1"

View File

@@ -169,6 +169,10 @@
<attr name="textStart" format="reference" /> <attr name="textStart" format="reference" />
<attr name="textEnd" format="reference" /> <attr name="textEnd" format="reference" />
<attr name="tickMark" 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>
<declare-styleable name="TintDrawable"> <declare-styleable name="TintDrawable">

View File

@@ -21,6 +21,8 @@ import android.content.res.TypedArray;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
@@ -28,17 +30,40 @@ import androidx.annotation.Nullable;
import androidx.core.content.res.TypedArrayUtils; import androidx.core.content.res.TypedArrayUtils;
import androidx.preference.PreferenceViewHolder; import androidx.preference.PreferenceViewHolder;
import com.android.internal.util.Preconditions;
import com.android.settings.R; 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 { public class LabeledSeekBarPreference extends SeekBarPreference {
private final int mTextStartId; private final int mTextStartId;
private final int mTextEndId; private final int mTextEndId;
private final int mTickMarkId; private final int mTickMarkId;
private final int mIconStartId;
private final int mIconEndId;
private final int mIconStartContentDescriptionId;
private final int mIconEndContentDescriptionId;
private OnPreferenceChangeListener mStopListener; private OnPreferenceChangeListener mStopListener;
@Nullable @Nullable
private CharSequence mSummary; private CharSequence mSummary;
private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener;
public LabeledSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr, public LabeledSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) { int defStyleRes) {
@@ -49,13 +74,30 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
final TypedArray styledAttrs = context.obtainStyledAttributes(attrs, final TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
R.styleable.LabeledSeekBarPreference); R.styleable.LabeledSeekBarPreference);
mTextStartId = styledAttrs.getResourceId( mTextStartId = styledAttrs.getResourceId(
R.styleable.LabeledSeekBarPreference_textStart, R.styleable.LabeledSeekBarPreference_textStart, /* defValue= */ 0);
R.string.summary_placeholder);
mTextEndId = styledAttrs.getResourceId( mTextEndId = styledAttrs.getResourceId(
R.styleable.LabeledSeekBarPreference_textEnd, R.styleable.LabeledSeekBarPreference_textEnd, /* defValue= */ 0);
R.string.summary_placeholder);
mTickMarkId = styledAttrs.getResourceId( mTickMarkId = styledAttrs.getResourceId(
R.styleable.LabeledSeekBarPreference_tickMark, /* defValue= */ 0); 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); mSummary = styledAttrs.getText(R.styleable.Preference_android_summary);
styledAttrs.recycle(); styledAttrs.recycle();
} }
@@ -71,14 +113,22 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
super.onBindViewHolder(holder); super.onBindViewHolder(holder);
final TextView startText = (TextView) holder.findViewById(android.R.id.text1); 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); startText.setText(mTextStartId);
endText.setText(mTextEndId); }
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) { if (mTickMarkId != 0) {
final Drawable tickMark = getContext().getDrawable(mTickMarkId); final Drawable tickMark = getContext().getDrawable(mTickMarkId);
final SeekBar seekBar = (SeekBar) holder.findViewById(
com.android.internal.R.id.seekbar);
seekBar.setTickMark(tickMark); seekBar.setTickMark(tickMark);
} }
@@ -90,19 +140,52 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
summary.setText(null); summary.setText(null);
summary.setVisibility(View.GONE); 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) { public void setOnPreferenceChangeStopListener(OnPreferenceChangeListener listener) {
mStopListener = 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 @Override
public void onStopTrackingTouch(SeekBar seekBar) { public void onStopTrackingTouch(SeekBar seekBar) {
super.onStopTrackingTouch(seekBar); super.onStopTrackingTouch(seekBar);
if (mSeekBarChangeListener != null) {
mSeekBarChangeListener.onStopTrackingTouch(seekBar);
}
if (mStopListener != null) { if (mStopListener != null) {
mStopListener.onPreferenceChange(this, seekBar.getProgress()); mStopListener.onPreferenceChange(this, seekBar.getProgress());
} }
// Need to update the icon enabled status
notifyChanged();
} }
@Override @Override
@@ -123,5 +206,68 @@ public class LabeledSeekBarPreference extends SeekBarPreference {
public CharSequence getSummary() { public CharSequence getSummary() {
return mSummary; 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);
}
} }

View File

@@ -18,12 +18,17 @@ package com.android.settings.gestures;
import static com.google.common.truth.Truth.assertThat; 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.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.SeekBar; import android.widget.SeekBar;
import android.widget.TextView; import android.widget.TextView;
@@ -31,42 +36,61 @@ import android.widget.TextView;
import androidx.preference.Preference; import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder; 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 com.android.settings.widget.LabeledSeekBarPreference;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations; import org.mockito.MockitoAnnotations;
import org.robolectric.Robolectric;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment; import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
/**
* Tests for {@link LabeledSeekBarPreference}.
*/
@RunWith(RobolectricTestRunner.class) @RunWith(RobolectricTestRunner.class)
@Config(shadows = {
ShadowUserManager.class
})
public class LabeledSeekBarPreferenceTest { public class LabeledSeekBarPreferenceTest {
private Context mContext; private Context mContext;
private PreferenceViewHolder mViewHolder; private PreferenceViewHolder mViewHolder;
private SeekBar mSeekBar; private SeekBar mSeekBar;
private TextView mSummary; private TextView mSummary;
private ViewGroup mIconStartFrame;
private ViewGroup mIconEndFrame;
private View mLabelFrame;
private LabeledSeekBarPreference mSeekBarPreference; private LabeledSeekBarPreference mSeekBarPreference;
@Mock @Mock
private Preference.OnPreferenceChangeListener mListener; private Preference.OnPreferenceChangeListener mListener;
@Mock
private SeekBar.OnSeekBarChangeListener mSeekBarChangeListener;
@Before @Before
public void setUp() { public void setUp() {
MockitoAnnotations.initMocks(this); MockitoAnnotations.initMocks(this);
mContext = RuntimeEnvironment.application; mContext = Mockito.spy(RuntimeEnvironment.application);
mSeekBarPreference = new LabeledSeekBarPreference(mContext, null); mSeekBarPreference = new LabeledSeekBarPreference(mContext, null);
LayoutInflater inflater = LayoutInflater.from(mContext); LayoutInflater inflater = LayoutInflater.from(mContext);
final View view = final View view =
inflater.inflate(mSeekBarPreference.getLayoutResource(), inflater.inflate(mSeekBarPreference.getLayoutResource(),
new LinearLayout(mContext), false); new LinearLayout(mContext), false);
mViewHolder = PreferenceViewHolder.createInstanceForTests(view); mViewHolder = PreferenceViewHolder.createInstanceForTests(view);
mSeekBar = (SeekBar) mViewHolder.findViewById(R.id.seekbar); mSeekBar = (SeekBar) mViewHolder.findViewById(com.android.internal.R.id.seekbar);
mSummary = (TextView) mViewHolder.findViewById(R.id.summary); 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 @Test
@@ -97,4 +121,122 @@ public class LabeledSeekBarPreferenceTest {
assertThat(mSummary.getText()).isEqualTo(""); assertThat(mSummary.getText()).isEqualTo("");
assertThat(mSummary.getVisibility()).isEqualTo(View.GONE); 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();
}
} }