From 6a893a54b613d45defbce3662ec3332bfc6ccc36 Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Tue, 11 Aug 2020 19:17:02 +0800 Subject: [PATCH 1/2] Refine and migrate the functions that related to update preference into the specific lifecycle. Goal: Avoid the screen to be scrolled or moved by itself after changing theme. Bug: 148785841 Test: make RunSettingsRoboTests ROBOTEST_FILTER=ToggleFeaturePreferenceFragment && make RunSettingsRoboTests ROBOTEST_FILTER=ToggleScreenMagnificationPreferenceFragmentTest Change-Id: I35a41a920194d6de01b635ce4c8461df2635ce1a --- .../ToggleFeaturePreferenceFragment.java | 187 ++++++++++-------- ...ScreenMagnificationPreferenceFragment.java | 33 ++-- 2 files changed, 122 insertions(+), 98 deletions(-) diff --git a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java index d0b83b6b8e1..130439115f4 100644 --- a/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleFeaturePreferenceFragment.java @@ -144,6 +144,21 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + // Need to be called as early as possible. Protected variables will be assigned here. + onProcessArguments(getArguments()); + + initAnimatedImagePreference(); + initToggleServiceDividerSwitchPreference(); + initGeneralCategory(); + initShortcutPreference(savedInstanceState); + initSettingsPreference(); + initHtmlTextPreference(); + initFooterPreference(); + + installActionBarToggleSwitch(); + + updateToggleServiceTitle(mToggleServiceDividerSwitchPreference); + mTouchExplorationStateChangeListener = isTouchExplorationEnabled -> { removeDialog(DialogEnums.EDIT_SHORTCUT); mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); @@ -158,84 +173,6 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference final SettingsActivity activity = (SettingsActivity) getActivity(); final SwitchBar switchBar = activity.getSwitchBar(); switchBar.hide(); - - // Need to be called as early as possible. Protected variables will be assigned here. - onProcessArguments(getArguments()); - - PreferenceScreen preferenceScreen = getPreferenceScreen(); - if (mImageUri != null) { - final int screenHalfHeight = getScreenHeightPixels(getPrefContext()) / /* half */ 2; - final AnimatedImagePreference animatedImagePreference = new AnimatedImagePreference( - getPrefContext()); - animatedImagePreference.setImageUri(mImageUri); - animatedImagePreference.setSelectable(false); - animatedImagePreference.setMaxHeight(screenHalfHeight); - preferenceScreen.addPreference(animatedImagePreference); - } - - mToggleServiceDividerSwitchPreference = new DividerSwitchPreference(getPrefContext()); - mToggleServiceDividerSwitchPreference.setKey(KEY_USE_SERVICE_PREFERENCE); - if (getArguments().containsKey(AccessibilitySettings.EXTRA_CHECKED)) { - final boolean enabled = getArguments().getBoolean(AccessibilitySettings.EXTRA_CHECKED); - mToggleServiceDividerSwitchPreference.setChecked(enabled); - } - - preferenceScreen.addPreference(mToggleServiceDividerSwitchPreference); - - updateToggleServiceTitle(mToggleServiceDividerSwitchPreference); - - final PreferenceCategory groupCategory = new PreferenceCategory(getPrefContext()); - groupCategory.setKey(KEY_GENERAL_CATEGORY); - groupCategory.setTitle(R.string.accessibility_screen_option); - preferenceScreen.addPreference(groupCategory); - - initShortcutPreference(savedInstanceState); - groupCategory.addPreference(mShortcutPreference); - - // Show the "Settings" menu as if it were a preference screen. - if (mSettingsTitle != null && mSettingsIntent != null) { - mSettingsPreference = new Preference(getPrefContext()); - mSettingsPreference.setTitle(mSettingsTitle); - mSettingsPreference.setIconSpaceReserved(true); - mSettingsPreference.setIntent(mSettingsIntent); - } - - // The downloaded app may not show Settings. The framework app has Settings. - if (mSettingsPreference != null) { - groupCategory.addPreference(mSettingsPreference); - } - - if (!TextUtils.isEmpty(mHtmlDescription)) { - final PreferenceCategory introductionCategory = new PreferenceCategory( - getPrefContext()); - final CharSequence title = getString(R.string.accessibility_introduction_title, - mPackageName); - introductionCategory.setKey(KEY_INTRODUCTION_CATEGORY); - introductionCategory.setTitle(title); - preferenceScreen.addPreference(introductionCategory); - - final HtmlTextPreference htmlTextPreference = new HtmlTextPreference(getPrefContext()); - htmlTextPreference.setSummary(mHtmlDescription); - htmlTextPreference.setImageGetter(mImageGetter); - htmlTextPreference.setSelectable(false); - introductionCategory.addPreference(htmlTextPreference); - } - - if (!TextUtils.isEmpty(mDescription)) { - createFooterPreference(mDescription); - } - - if (TextUtils.isEmpty(mHtmlDescription) && TextUtils.isEmpty(mDescription)) { - final CharSequence defaultDescription = getText( - R.string.accessibility_service_default_description); - createFooterPreference(defaultDescription); - } - } - - @Override - public void onActivityCreated(Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - installActionBarToggleSwitch(); } @Override @@ -444,6 +381,93 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference return drawable; } + private void initAnimatedImagePreference() { + if (mImageUri == null) { + return; + } + + final int screenHalfHeight = getScreenHeightPixels(getPrefContext()) / /* half */ 2; + final AnimatedImagePreference animatedImagePreference = + new AnimatedImagePreference(getPrefContext()); + animatedImagePreference.setImageUri(mImageUri); + animatedImagePreference.setSelectable(false); + animatedImagePreference.setMaxHeight(screenHalfHeight); + + getPreferenceScreen().addPreference(animatedImagePreference); + } + + private void initToggleServiceDividerSwitchPreference() { + mToggleServiceDividerSwitchPreference = new DividerSwitchPreference(getPrefContext()); + mToggleServiceDividerSwitchPreference.setKey(KEY_USE_SERVICE_PREFERENCE); + if (getArguments().containsKey(AccessibilitySettings.EXTRA_CHECKED)) { + final boolean enabled = getArguments().getBoolean(AccessibilitySettings.EXTRA_CHECKED); + mToggleServiceDividerSwitchPreference.setChecked(enabled); + } + + getPreferenceScreen().addPreference(mToggleServiceDividerSwitchPreference); + } + + private void initGeneralCategory() { + final PreferenceCategory generalCategory = new PreferenceCategory(getPrefContext()); + generalCategory.setKey(KEY_GENERAL_CATEGORY); + generalCategory.setTitle(R.string.accessibility_screen_option); + + getPreferenceScreen().addPreference(generalCategory); + } + + protected void initSettingsPreference() { + if (mSettingsTitle == null || mSettingsIntent == null) { + return; + } + + // Show the "Settings" menu as if it were a preference screen. + mSettingsPreference = new Preference(getPrefContext()); + mSettingsPreference.setTitle(mSettingsTitle); + mSettingsPreference.setIconSpaceReserved(true); + mSettingsPreference.setIntent(mSettingsIntent); + + final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); + generalCategory.addPreference(mSettingsPreference); + } + + private void initIntroductionCategory() { + final PreferenceCategory introductionCategory = new PreferenceCategory(getPrefContext()); + final CharSequence title = + getString(R.string.accessibility_introduction_title, mPackageName); + introductionCategory.setKey(KEY_INTRODUCTION_CATEGORY); + introductionCategory.setTitle(title); + + getPreferenceScreen().addPreference(introductionCategory); + } + + private void initHtmlTextPreference() { + if (TextUtils.isEmpty(mHtmlDescription)) { + return; + } + + initIntroductionCategory(); + + final HtmlTextPreference htmlTextPreference = new HtmlTextPreference(getPrefContext()); + htmlTextPreference.setSummary(mHtmlDescription); + htmlTextPreference.setImageGetter(mImageGetter); + htmlTextPreference.setSelectable(false); + + final PreferenceCategory introductionCategory = findPreference(KEY_INTRODUCTION_CATEGORY); + introductionCategory.addPreference(htmlTextPreference); + } + + private void initFooterPreference() { + if (!TextUtils.isEmpty(mDescription)) { + createFooterPreference(mDescription); + } + + if (TextUtils.isEmpty(mHtmlDescription) && TextUtils.isEmpty(mDescription)) { + final CharSequence defaultDescription = + getText(R.string.accessibility_service_default_description); + createFooterPreference(defaultDescription); + } + } + static final class AccessibilityUserShortcutType { private static final char COMPONENT_NAME_SEPARATOR = ':'; private static final TextUtils.SimpleStringSplitter sStringColonSplitter = @@ -653,7 +677,7 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference } } - private void initShortcutPreference(Bundle savedInstanceState) { + protected void initShortcutPreference(Bundle savedInstanceState) { // Restore the user shortcut type. if (savedInstanceState != null && savedInstanceState.containsKey(EXTRA_SHORTCUT_TYPE)) { mUserShortcutTypesCache = savedInstanceState.getInt(EXTRA_SHORTCUT_TYPE, @@ -668,6 +692,9 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference final CharSequence title = getString(R.string.accessibility_shortcut_title, mPackageName); mShortcutPreference.setTitle(title); + + final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); + generalCategory.addPreference(mShortcutPreference); } protected void updateShortcutPreference() { @@ -682,7 +709,7 @@ public abstract class ToggleFeaturePreferenceFragment extends SettingsPreference mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); } - private String getShortcutPreferenceKey() { + protected String getShortcutPreferenceKey() { return KEY_SHORTCUT_PREFERENCE; } diff --git a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java index 51a861dc18e..cf4d4d52a5a 100644 --- a/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java +++ b/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragment.java @@ -39,6 +39,7 @@ import android.widget.CheckBox; import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; import com.android.internal.annotations.VisibleForTesting; import com.android.settings.R; @@ -60,7 +61,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends ToggleFeaturePreferenceFragment { private static final String EXTRA_SHORTCUT_TYPE = "shortcut_type"; - private static final String KEY_SHORTCUT_PREFERENCE = "shortcut_preference"; private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener; private int mUserShortcutType = UserShortcutType.EMPTY; private CheckBox mSoftwareTypeCheckBox; @@ -95,18 +95,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends return super.onCreateView(inflater, container, savedInstanceState); } - @Override - public void onViewCreated(View view, Bundle savedInstanceState) { - initShortcutPreference(); - - mSettingsPreference = new Preference(getPrefContext()); - mSettingsPreference.setTitle(R.string.accessibility_menu_item_settings); - mSettingsPreference.setFragment(MagnificationSettingsFragment.class.getName()); - mSettingsPreference.setPersistent(false); - - super.onViewCreated(view, savedInstanceState); - } - @Override public void onSaveInstanceState(Bundle outState) { outState.putInt(EXTRA_SHORTCUT_TYPE, mUserShortcutTypesCache); @@ -120,9 +108,6 @@ public class ToggleScreenMagnificationPreferenceFragment extends final AccessibilityManager am = getPrefContext().getSystemService( AccessibilityManager.class); am.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); - - updateShortcutPreferenceData(); - updateShortcutPreference(); } @Override @@ -162,6 +147,14 @@ public class ToggleScreenMagnificationPreferenceFragment extends }); } + @Override + protected void initSettingsPreference() { + mSettingsPreference = new Preference(getPrefContext()); + mSettingsPreference.setTitle(R.string.accessibility_menu_item_settings); + mSettingsPreference.setFragment(MagnificationSettingsFragment.class.getName()); + mSettingsPreference.setPersistent(false); + } + private void initializeDialogCheckBox(AlertDialog dialog) { final View dialogSoftwareView = dialog.findViewById(R.id.software_shortcut); mSoftwareTypeCheckBox = dialogSoftwareView.findViewById(R.id.checkbox); @@ -374,15 +367,19 @@ public class ToggleScreenMagnificationPreferenceFragment extends } } - private void initShortcutPreference() { + @Override + protected void initShortcutPreference(Bundle savedInstanceState) { mShortcutPreference = new ShortcutPreference(getPrefContext(), null); mShortcutPreference.setPersistent(false); - mShortcutPreference.setKey(KEY_SHORTCUT_PREFERENCE); + mShortcutPreference.setKey(getShortcutPreferenceKey()); mShortcutPreference.setSummary(getShortcutTypeSummary(getPrefContext())); mShortcutPreference.setOnClickCallback(this); final CharSequence title = getString(R.string.accessibility_shortcut_title, mPackageName); mShortcutPreference.setTitle(title); + + final PreferenceCategory generalCategory = findPreference(KEY_GENERAL_CATEGORY); + generalCategory.addPreference(mShortcutPreference); } @Override From 896e18012d9bc910ece8a15eb3c2f5d2b3514e26 Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Fri, 14 Aug 2020 16:27:31 +0800 Subject: [PATCH 2/2] Fix the screen will move by itself when enabled Color correction. Root Cause: Using the listener to update the UI dynamically might have some time delay. Solution: Refactor to another implementation and avoid using the listener to update it. Additional condition: Add height restriction in preference to avoid the palette view to cover whole screen. Bug: 148785841 Test: make RunSettingsRoboTests ROBOTEST_FILTER=PaletteListPreferenceTest Change-Id: I6a854e16321b3426e2f8ff65c6404036d55caed4 --- res/layout/daltonizer_preview.xml | 10 +- res/values/arrays.xml | 4 +- .../accessibility/PaletteListPreference.java | 160 +++++++--- .../accessibility/PaletteListView.java | 301 ------------------ .../PaletteListPreferenceTest.java | 64 ++++ .../accessibility/PaletteListViewTest.java | 65 ---- 6 files changed, 192 insertions(+), 412 deletions(-) delete mode 100644 src/com/android/settings/accessibility/PaletteListView.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/PaletteListPreferenceTest.java delete mode 100644 tests/robotests/src/com/android/settings/accessibility/PaletteListViewTest.java diff --git a/res/layout/daltonizer_preview.xml b/res/layout/daltonizer_preview.xml index bdf1070b744..de462f1f5d7 100644 --- a/res/layout/daltonizer_preview.xml +++ b/res/layout/daltonizer_preview.xml @@ -17,13 +17,13 @@ - - + android:orientation="vertical" + android:importantForAccessibility="noHideDescendants"/> diff --git a/res/values/arrays.xml b/res/values/arrays.xml index 26bdddfb1a1..fa5bef90e7d 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -1468,7 +1468,7 @@ - + @string/color_red @string/color_orange @string/color_yellow @@ -1479,7 +1479,7 @@ - + @color/palette_list_color_red @color/palette_list_color_orange @color/palette_list_color_yellow diff --git a/src/com/android/settings/accessibility/PaletteListPreference.java b/src/com/android/settings/accessibility/PaletteListPreference.java index c5c42054a64..ac552ebc0e0 100644 --- a/src/com/android/settings/accessibility/PaletteListPreference.java +++ b/src/com/android/settings/accessibility/PaletteListPreference.java @@ -16,23 +16,58 @@ package com.android.settings.accessibility; -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.view.ViewTreeObserver; -import android.widget.FrameLayout; -import android.widget.ListView; +import static android.graphics.drawable.GradientDrawable.Orientation; +import static com.android.settings.accessibility.AccessibilityUtil.getScreenHeightPixels; +import static com.android.settings.accessibility.AccessibilityUtil.getScreenWidthPixels; + +import static com.google.common.primitives.Ints.max; + +import android.content.Context; +import android.graphics.Paint.FontMetrics; +import android.graphics.drawable.GradientDrawable; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.ColorInt; +import androidx.annotation.IntDef; import androidx.preference.Preference; import androidx.preference.PreferenceViewHolder; import com.android.settings.R; -/** Preference that easier preview by matching name to color. */ -public class PaletteListPreference extends Preference { +import com.google.common.primitives.Floats; +import com.google.common.primitives.Ints; - private ListView mListView; - private ViewTreeObserver.OnPreDrawListener mPreDrawListener; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +/** Preference that easier preview by matching name to color. */ +public final class PaletteListPreference extends Preference { + + private final List mGradientColors = new ArrayList<>(); + private final List mGradientOffsets = new ArrayList<>(); + + @IntDef({ + Position.START, + Position.CENTER, + Position.END, + }) + @Retention(RetentionPolicy.SOURCE) + @interface Position { + int START = 0; + int CENTER = 1; + int END = 2; + } /** * Constructs a new PaletteListPreference with the given context's theme and the supplied @@ -61,47 +96,94 @@ public class PaletteListPreference extends Preference { public PaletteListPreference(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); setLayoutResource(R.layout.daltonizer_preview); - initPreDrawListener(); } @Override public void onBindViewHolder(PreferenceViewHolder holder) { super.onBindViewHolder(holder); - final View rootView = holder.itemView; - mListView = rootView.findViewById(R.id.palette_listView); - if (mPreDrawListener != null) { - mListView.getViewTreeObserver().addOnPreDrawListener(mPreDrawListener); + final ViewGroup paletteView = holder.itemView.findViewById(R.id.palette_view); + initPaletteAttributes(getContext()); + initPaletteView(getContext(), paletteView); + } + + private void initPaletteAttributes(Context context) { + final int defaultColor = context.getColor(R.color.palette_list_gradient_background); + mGradientColors.add(Position.START, defaultColor); + mGradientColors.add(Position.CENTER, defaultColor); + mGradientColors.add(Position.END, defaultColor); + + mGradientOffsets.add(Position.START, /* element= */ 0.0f); + mGradientOffsets.add(Position.CENTER, /* element= */ 0.5f); + mGradientOffsets.add(Position.END, /* element= */ 1.0f); + } + + private void initPaletteView(Context context, ViewGroup rootView) { + if (rootView.getChildCount() > 0) { + rootView.removeAllViews(); + } + + final List paletteColors = getPaletteColors(context); + final List paletteData = getPaletteData(context); + + final float textPadding = + context.getResources().getDimension(R.dimen.accessibility_layout_margin_start_end); + final String maxLengthData = + Collections.max(paletteData, Comparator.comparing(String::length)); + final int textWidth = getTextWidth(context, maxLengthData); + final float textBound = (textWidth + textPadding) / getScreenWidthPixels(context); + mGradientOffsets.set(Position.CENTER, textBound); + + final int screenHalfHeight = getScreenHeightPixels(context) / 2; + final int paletteItemHeight = + max(screenHalfHeight / paletteData.size(), getTextLineHeight(context)); + + for (int i = 0; i < paletteData.size(); ++i) { + final TextView textView = new TextView(context); + textView.setText(paletteData.get(i)); + textView.setHeight(paletteItemHeight); + textView.setPaddingRelative(Math.round(textPadding), 0, 0, 0); + textView.setGravity(Gravity.CENTER_VERTICAL); + textView.setBackground(createGradientDrawable(rootView, paletteColors.get(i))); + + rootView.addView(textView); } } - private void initPreDrawListener() { - mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() { - @Override - public boolean onPreDraw() { - if (mListView == null) { - return false; - } + private GradientDrawable createGradientDrawable(ViewGroup rootView, @ColorInt int color) { + mGradientColors.set(Position.END, color); - final int listViewHeight = mListView.getMeasuredHeight(); - final int listViewWidth = mListView.getMeasuredWidth(); + final GradientDrawable gradientDrawable = new GradientDrawable(); + final Orientation orientation = + rootView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL + ? Orientation.RIGHT_LEFT + : Orientation.LEFT_RIGHT; + gradientDrawable.setOrientation(orientation); + gradientDrawable.setColors(Ints.toArray(mGradientColors), Floats.toArray(mGradientOffsets)); - // Removes the callback after get result of measure view. - final ViewTreeObserver viewTreeObserver = mListView.getViewTreeObserver(); - if (viewTreeObserver.isAlive()) { - viewTreeObserver.removeOnPreDrawListener(this); - } - mPreDrawListener = null; + return gradientDrawable; + } - // Resets layout parameters to display whole items from listView. - final FrameLayout.LayoutParams layoutParams = - (FrameLayout.LayoutParams) mListView.getLayoutParams(); - layoutParams.height = listViewHeight * mListView.getAdapter().getCount(); - layoutParams.width = listViewWidth; - mListView.setLayoutParams(layoutParams); + private List getPaletteColors(Context context) { + final int[] paletteResources = + context.getResources().getIntArray(R.array.setting_palette_colors); + return Arrays.stream(paletteResources).boxed().collect(Collectors.toList()); + } - return true; - } - }; + private List getPaletteData(Context context) { + final String[] paletteResources = + context.getResources().getStringArray(R.array.setting_palette_data); + return Arrays.asList(paletteResources); + } + + private int getTextWidth(Context context, String text) { + final TextView tempView = new TextView(context); + return Math.round(tempView.getPaint().measureText(text)); + } + + private int getTextLineHeight(Context context) { + final TextView tempView = new TextView(context); + final FontMetrics fontMetrics = tempView.getPaint().getFontMetrics(); + return Math.round(fontMetrics.bottom - fontMetrics.top); } } diff --git a/src/com/android/settings/accessibility/PaletteListView.java b/src/com/android/settings/accessibility/PaletteListView.java deleted file mode 100644 index ef010e2d979..00000000000 --- a/src/com/android/settings/accessibility/PaletteListView.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * 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.settings.accessibility; - -import android.annotation.NonNull; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Color; -import android.graphics.drawable.GradientDrawable; -import android.graphics.drawable.GradientDrawable.Orientation; -import android.util.AttributeSet; -import android.util.DisplayMetrics; -import android.view.Display; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.BaseAdapter; -import android.widget.ListView; -import android.widget.TextView; - -import androidx.annotation.VisibleForTesting; - -import com.android.settings.R; - -import com.google.common.collect.Iterables; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; - -/** - * Custom ListView {@link ListView} which displays palette to deploy the color code preview. - * - *

The preview shows gradient from color white to specific color code on each list view item, in - * addition, text view adjusts the attribute of width for adapting the text length. - * - *

The text cannot fills the whole view for ensuring the gradient color preview can purely - * display also the view background shows the color beside the text variable end point. - */ -public class PaletteListView extends ListView { - private final Context mContext; - private final DisplayAdapter mDisplayAdapter; - private final LayoutInflater mLayoutInflater; - private final String mDefaultGradientColorCodeString; - private final int mDefaultGradientColor; - private float mTextBound; - private static final float LANDSCAPE_MAX_WIDTH_PERCENTAGE = 100f; - - public PaletteListView(Context context) { - this(context, null); - } - - public PaletteListView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public PaletteListView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - mContext = context; - mDisplayAdapter = new DisplayAdapter(); - mLayoutInflater = LayoutInflater.from(context); - mDefaultGradientColorCodeString = - getResources().getString(R.color.palette_list_gradient_background); - mDefaultGradientColor = - getResources().getColor(R.color.palette_list_gradient_background, null); - mTextBound = 0.0f; - init(); - } - - private static int getScreenWidth(WindowManager windowManager) { - final Display display = windowManager.getDefaultDisplay(); - final DisplayMetrics displayMetrics = new DisplayMetrics(); - display.getMetrics(displayMetrics); - return displayMetrics.widthPixels; - } - - private void init() { - final TypedArray colorNameArray = getResources().obtainTypedArray( - R.array.setting_palette_colors); - final TypedArray colorCodeArray = getResources().obtainTypedArray( - R.array.setting_palette_data); - final int colorNameArrayLength = colorNameArray.length(); - final List colorList = new ArrayList<>(); - computeTextWidthBounds(colorNameArray); - - for (int index = 0; index < colorNameArrayLength; index++) { - colorList.add( - new ColorAttributes( - /* colorName= */ colorNameArray.getString(index), - /* colorCode= */ colorCodeArray.getColor(index, mDefaultGradientColor), - /* textBound= */ mTextBound, - /* gradientDrawable= */ - new GradientDrawable(Orientation.LEFT_RIGHT, null))); - } - - mDisplayAdapter.setColorList(colorList); - setAdapter(mDisplayAdapter); - setDividerHeight(/* height= */ 0); - } - - /** - * Sets string array that required the color name and color code for deploy the new color - * preview. - * - *

The parameters not allow null define but two array length inconsistent are acceptable, in - * addition, to prevent IndexOutOfBoundsException the algorithm will check array data, and base - * on the array size to display data, or fills color code array if length less than other. - * - * @param colorNames a string array of color name - * @param colorCodes a string array of color code - * @return true if new array data apply successful - */ - @VisibleForTesting - boolean setPaletteListColors(@NonNull String[] colorNames, @NonNull String[] colorCodes) { - if (colorNames == null || colorCodes == null) { - return false; - } - - final int colorNameArrayLength = colorNames.length; - final int colorCodeArrayLength = colorCodes.length; - final List colorList = new ArrayList<>(); - final String[] colorCodeArray = fillColorCodeArray(colorCodes, colorNameArrayLength, - colorCodeArrayLength); - computeTextWidthBounds(colorNames); - - for (int index = 0; index < colorNameArrayLength; index++) { - colorList.add( - new ColorAttributes( - /* colorName= */ colorNames[index], - /* colorCode= */ Color.parseColor(colorCodeArray[index]), - /* textBound= */ mTextBound, - /* gradientDrawable= */ - new GradientDrawable(Orientation.LEFT_RIGHT, null))); - } - - mDisplayAdapter.setColorList(colorList); - mDisplayAdapter.notifyDataSetChanged(); - return true; - } - - private String[] fillColorCodeArray(String[] colorCodes, int colorNameArrayLength, - int colorCodeArrayLength) { - if (colorNameArrayLength == colorCodeArrayLength - || colorNameArrayLength < colorCodeArrayLength) { - return colorCodes; - } - - final String[] colorCodeArray = new String[colorNameArrayLength]; - for (int index = 0; index < colorNameArrayLength; index++) { - if (index < colorCodeArrayLength) { - colorCodeArray[index] = colorCodes[index]; - } else { - colorCodeArray[index] = mDefaultGradientColorCodeString; - } - } - return colorCodeArray; - } - - private void computeTextWidthBounds(TypedArray colorNameTypedArray) { - final int colorNameArrayLength = colorNameTypedArray.length(); - final String[] colorNames = new String[colorNameArrayLength]; - for (int index = 0; index < colorNameArrayLength; index++) { - colorNames[index] = colorNameTypedArray.getString(index); - } - - measureBound(colorNames); - } - - private void computeTextWidthBounds(String[] colorNameArray) { - final int colorNameArrayLength = colorNameArray.length; - final String[] colorNames = new String[colorNameArrayLength]; - for (int index = 0; index < colorNameArrayLength; index++) { - colorNames[index] = colorNameArray[index]; - } - - measureBound(colorNames); - } - - private void measureBound(String[] dataArray) { - final WindowManager windowManager = (WindowManager) mContext.getSystemService( - Context.WINDOW_SERVICE); - final View view = mLayoutInflater.inflate(R.layout.palette_listview_item, null); - final TextView textView = view.findViewById(R.id.item_textview); - final List colorNameList = new ArrayList<>(Arrays.asList(dataArray)); - Collections.sort(colorNameList, Comparator.comparing(String::length)); - // Gets the last index of list which sort by text length. - textView.setText(Iterables.getLast(colorNameList)); - - final float textWidth = textView.getPaint().measureText(textView.getText().toString()); - // Computes rate of text width compare to screen width, and measures the round the double - // to two decimal places manually. - final float textBound = Math.round( - textWidth / getScreenWidth(windowManager) * LANDSCAPE_MAX_WIDTH_PERCENTAGE) - / LANDSCAPE_MAX_WIDTH_PERCENTAGE; - - // Left padding and right padding with color preview. - final float paddingPixel = getResources().getDimension( - R.dimen.accessibility_layout_margin_start_end); - final float paddingWidth = - Math.round(paddingPixel / getScreenWidth(windowManager) - * LANDSCAPE_MAX_WIDTH_PERCENTAGE) / LANDSCAPE_MAX_WIDTH_PERCENTAGE; - mTextBound = textBound + paddingWidth + paddingWidth; - } - - private static class ViewHolder { - public TextView textView; - } - - /** An adapter that converts color text title and color code to text views. */ - private final class DisplayAdapter extends BaseAdapter { - - private List mColorList; - - @Override - public int getCount() { - return mColorList.size(); - } - - @Override - public Object getItem(int position) { - return mColorList.get(position); - } - - @Override - public long getItemId(int position) { - return position; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - final ViewHolder viewHolder; - final ColorAttributes paletteAttribute = mColorList.get(position); - final String colorName = paletteAttribute.getColorName(); - final GradientDrawable gradientDrawable = paletteAttribute.getGradientDrawable(); - - if (convertView == null) { - convertView = mLayoutInflater.inflate(R.layout.palette_listview_item, null); - viewHolder = new ViewHolder(); - viewHolder.textView = convertView.findViewById(R.id.item_textview); - convertView.setTag(viewHolder); - } else { - viewHolder = (ViewHolder) convertView.getTag(); - } - - viewHolder.textView.setText(colorName); - viewHolder.textView.setBackground(gradientDrawable); - return convertView; - } - - protected void setColorList(List colorList) { - mColorList = colorList; - } - } - - private final class ColorAttributes { - private final int mColorIndex = 2; // index for inject color. - private final int mColorOffsetIndex = 1; // index for offset effect. - private final String mColorName; - private final GradientDrawable mGradientDrawable; - private final int[] mGradientColors = - {/* startColor=*/ mDefaultGradientColor, /* centerColor=*/ mDefaultGradientColor, - /* endCode= */ 0}; - private final float[] mGradientOffsets = - {/* starOffset= */ 0.0f, /* centerOffset= */ 0.5f, /* endOffset= */ 1.0f}; - - ColorAttributes( - String colorName, int colorCode, float textBound, - GradientDrawable gradientDrawable) { - mGradientColors[mColorIndex] = colorCode; - mGradientOffsets[mColorOffsetIndex] = textBound; - gradientDrawable.setColors(mGradientColors, mGradientOffsets); - mColorName = colorName; - mGradientDrawable = gradientDrawable; - } - - public String getColorName() { - return mColorName; - } - - public GradientDrawable getGradientDrawable() { - return mGradientDrawable; - } - } -} diff --git a/tests/robotests/src/com/android/settings/accessibility/PaletteListPreferenceTest.java b/tests/robotests/src/com/android/settings/accessibility/PaletteListPreferenceTest.java new file mode 100644 index 00000000000..759b180206f --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/PaletteListPreferenceTest.java @@ -0,0 +1,64 @@ +/* + * 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.settings.accessibility; + +import static org.junit.Assert.assertEquals; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.preference.PreferenceViewHolder; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** Tests for {@link PaletteListPreference}. */ +@RunWith(RobolectricTestRunner.class) +public final class PaletteListPreferenceTest { + + private PaletteListPreference mPaletteListPreference; + private PreferenceViewHolder mPreferenceViewHolder; + private final Context mContext = ApplicationProvider.getApplicationContext(); + + @Before + public void initObjects() { + mPaletteListPreference = new PaletteListPreference(mContext, null); + + final LayoutInflater inflater = LayoutInflater.from(mContext); + final View view = + inflater.inflate(R.layout.daltonizer_preview, null); + mPreferenceViewHolder = PreferenceViewHolder.createInstanceForTests(view); + } + + @Test + public void initPaletteView_success() { + mPaletteListPreference.onBindViewHolder(mPreferenceViewHolder); + + final ViewGroup viewGroup = + mPreferenceViewHolder.itemView.findViewById(R.id.palette_view); + final int expectedCount = + mContext.getResources().getStringArray(R.array.setting_palette_data).length; + assertEquals(expectedCount, viewGroup.getChildCount()); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/PaletteListViewTest.java b/tests/robotests/src/com/android/settings/accessibility/PaletteListViewTest.java deleted file mode 100644 index 13e76d71392..00000000000 --- a/tests/robotests/src/com/android/settings/accessibility/PaletteListViewTest.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * 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.settings.accessibility; - -import static com.google.common.truth.Truth.assertThat; - -import android.content.Context; - -import androidx.test.core.app.ApplicationProvider; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.RobolectricTestRunner; - -/** Tests for {@link PaletteListView} */ -@RunWith(RobolectricTestRunner.class) -public class PaletteListViewTest { - - private final Context mContext = ApplicationProvider.getApplicationContext(); - private PaletteListView mPaletteListView; - - @Before - public void setUp() { - mPaletteListView = new PaletteListView(mContext); - } - - @Test - public void setColors_applySameLengthArray_configureSuccessful() { - final String[] colorName = {"White", "Black", "Yellow"}; - final String[] colorCode = {"#ffffff", "#000000", "#f9ab00"}; - - assertThat(mPaletteListView.setPaletteListColors(colorName, colorCode)).isTrue(); - } - - @Test - public void setColors_applyDifferentLengthArray_configureSuccessful() { - final String[] colorName = {"White", "Black", "Yellow", "Orange", "Red"}; - final String[] colorCode = {"#ffffff", "#000000", "#f9ab00"}; - - assertThat(mPaletteListView.setPaletteListColors(colorName, colorCode)).isTrue(); - } - - @Test - public void setColors_configureFailed() { - final String[] colorName = null; - final String[] colorCode = null; - - assertThat(mPaletteListView.setPaletteListColors(colorName, colorCode)).isFalse(); - } -}