Merge "Create palette preview for color correction."

This commit is contained in:
Jasper Chang
2020-01-07 03:20:54 +00:00
committed by Android (Google) Code Review
10 changed files with 589 additions and 20 deletions

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/preview_viewport"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.android.settings.accessibility.PaletteListView
android:id="@+id/palette_listView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@null" />
</FrameLayout>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
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
-->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/item_textview"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:textSize="20dp"
android:maxLength="20"
android:singleLine="true"/>
</FrameLayout>

View File

@@ -22,11 +22,21 @@
<color name="homepage_card_dismissal_background">@*android:color/material_grey_900</color>
<color name="contextual_card_background">@*android:color/material_grey_900</color>
<color name="search_bar_background">@*android:color/material_grey_800</color>
<!-- Dialog background color -->
<!-- Dialog background color. -->
<color name="dialog_background">@*android:color/material_grey_800</color>
<color name="notification_importance_selection_bg">@*android:color/material_grey_800</color>
<color name="notification_importance_button_selected">#AECBFA</color> <!-- material blue 200 -->
<color name="notification_importance_button_unselected">#5F6368</color>
<color name="face_intro_outline">?android:attr/colorAccent</color>
<!-- Palette list preference colors. -->
<color name="palette_list_gradient_background">@android:color/black</color>
<color name="palette_list_color_red">@color/palette_list_dark_mode_color_red</color>
<color name="palette_list_color_orange">@color/palette_list_dark_mode_color_orange</color>
<color name="palette_list_color_yellow">@color/palette_list_dark_mode_color_yellow</color>
<color name="palette_list_color_green">@color/palette_list_dark_mode_color_green</color>
<color name="palette_list_color_cyan">@color/palette_list_dark_mode_color_cyan</color>
<color name="palette_list_color_blue">@color/palette_list_dark_mode_color_blue</color>
<color name="palette_list_color_purple">@color/palette_list_dark_mode_color_purple</color>
<color name="palette_list_color_pink">@color/palette_list_dark_mode_color_pink</color>
</resources>

View File

@@ -1469,4 +1469,27 @@
<item>"com.google.android.googlequicksearchbox"</item>
</string-array>
<!-- Array of titles palette list for accessibility. -->
<string-array name="setting_palette_colors" translatable="false" >
<item>@string/color_red</item>
<item>@string/color_orange</item>
<item>@string/color_yellow</item>
<item>@string/color_green</item>
<item>@string/color_cyan</item>
<item>@string/color_blue</item>
<item>@string/color_purple</item>
<item>@string/color_pink</item>
</string-array>
<!-- Values for palette list view preference. -->
<array name="setting_palette_data" translatable="false" >
<item>@color/palette_list_color_red</item>
<item>@color/palette_list_color_orange</item>
<item>@color/palette_list_color_yellow</item>
<item>@color/palette_list_color_green</item>
<item>@color/palette_list_color_cyan</item>
<item>@color/palette_list_color_blue</item>
<item>@color/palette_list_color_purple</item>
<item>@color/palette_list_color_pink</item>
</array>
</resources>

View File

@@ -148,4 +148,26 @@
<color name="face_intro_outline">#ffdadce0</color>
<color name="back_gesture_indicator">#4182ef</color>
<!-- Palette list preference colors. -->
<color name="palette_list_gradient_background">@android:color/white</color>
<color name="palette_list_color_red">#d93025</color> <!-- Material Red 600 -->
<color name="palette_list_color_orange">#e8710a</color> <!-- Material Orange 600 -->
<color name="palette_list_color_yellow">#f9ab00</color> <!-- Material Yellow 600 -->
<color name="palette_list_color_green">#1e8e3e</color> <!-- Material Green 600 -->
<color name="palette_list_color_cyan">#12b5cb</color> <!-- Material Cyan 600 -->
<color name="palette_list_color_blue">#1a73e8</color> <!-- Material Blue 600 -->
<color name="palette_list_color_purple">#9334e6</color> <!-- Material Purple 600 -->
<color name="palette_list_color_pink">#e52592</color> <!-- Material Pink 600 -->
<!-- Palette list preference dark mode colors. -->
<color name="palette_list_dark_mode_color_red">#f28b82</color> <!-- Material Red 300 -->
<color name="palette_list_dark_mode_color_orange">#fcad70</color> <!-- Material Orange 300 -->
<color name="palette_list_dark_mode_color_yellow">#fdd663</color> <!-- Material Yellow 300 -->
<color name="palette_list_dark_mode_color_green">#81c995</color> <!-- Material Green 300 -->
<color name="palette_list_dark_mode_color_cyan">#78d9ec</color> <!-- Material Cyan 300 -->
<color name="palette_list_dark_mode_color_blue">#8AB4F8</color> <!-- Material Blue 300 -->
<color name="palette_list_dark_mode_color_purple">#c58af9</color> <!-- Material Purple 300 -->
<color name="palette_list_dark_mode_color_pink">#ff8bcb</color> <!-- Material Pink 300 -->
</resources>

View File

@@ -7232,6 +7232,8 @@
<string name="color_orange">Orange</string>
<!-- Purple label. [CHAR LIMIT=40] -->
<string name="color_purple">Purple</string>
<!-- Pink label. [CHAR LIMIT=40] -->
<string name="color_pink">Pink</string>
<!-- Message informing the user that no SIM card is inserted [CHAR LIMIT=60] -->
<string name="sim_no_inserted_msg">No SIM cards inserted</string>
<!-- SIM status title [CHAR LIMIT=40] -->

View File

@@ -21,6 +21,13 @@
android:persistent="false"
android:title="@string/accessibility_display_daltonizer_preference_title">
<com.android.settings.accessibility.PaletteListPreference
android:key="daltonizer_preview"
android:persistent="false"
android:selectable="false"
android:title="@string/summary_placeholder"
settings:searchable="false"/>
<com.android.settingslib.widget.RadioButtonPreference
android:key="daltonizer_mode_deuteranomaly"
android:persistent="false"

View File

@@ -0,0 +1,88 @@
/*
* 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.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
import android.widget.ListView;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
import com.android.settingslib.widget.R;
/** Preference that easier preview by matching name to color. */
public class PaletteListPreference extends Preference {
/**
* Constructs a new PaletteListPreference with the given context's theme and the supplied
* attribute set.
*
* @param context The Context this is associated with, through which it can access the current
* theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
*/
public PaletteListPreference(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Constructs a new PaletteListPreference with the given context's theme, the supplied
* attribute set, and default style attribute.
*
* @param context The Context this is associated with, through which it can access the
* current theme, resources, etc.
* @param attrs The attributes of the XML tag that is inflating the view.
* @param defStyleAttr An attribute in the current theme that contains a reference to a style
* resource that supplies default
* values for the view. Can be 0 to not look for
* defaults.
*/
public PaletteListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setLayoutResource(R.layout.daltonizer_preview);
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
final View rootView = holder.itemView;
final ListView listView = rootView.findViewById(R.id.palette_listView);
listView.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
final int listViewHeight = listView.getMeasuredHeight();
final int listViewWidth = listView.getMeasuredWidth();
// Removes the callback after get result of measure view.
listView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
// Resets layout parameters to display whole items from listView.
final FrameLayout.LayoutParams layoutParams =
(FrameLayout.LayoutParams) listView.getLayoutParams();
layoutParams.height = listViewHeight * listView.getAdapter().getCount();
layoutParams.width = listViewWidth;
listView.setLayoutParams(layoutParams);
listView.invalidateViews();
}
});
}
}

View File

@@ -0,0 +1,292 @@
/*
* 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.settingslib.widget.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.
*
* <p>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.
*
* <p>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 static final float VIEW_PITCH = 0.05f;
private final Context mContext;
private final DisplayAdapter mDisplayAdapter;
private final LayoutInflater mLayoutInflater;
private final String mDefaultGradientColorCodeString;
private final int mDefaultGradientColor;
private float mTextBound;
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<ColorAttributes> 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.
*
* <p>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<ColorAttributes> 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<String> 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) * 100) / 100f;
mTextBound = textBound + VIEW_PITCH;
}
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<ColorAttributes> 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<ColorAttributes> 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;
}
}
}

View File

@@ -0,0 +1,65 @@
/*
* 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();
}
}