1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.settings.accessibility;
18 
19 import static android.graphics.drawable.GradientDrawable.Orientation;
20 
21 import static com.android.settings.accessibility.AccessibilityUtil.getScreenHeightPixels;
22 import static com.android.settings.accessibility.AccessibilityUtil.getScreenWidthPixels;
23 
24 import static com.google.common.primitives.Ints.max;
25 
26 import android.content.Context;
27 import android.graphics.Paint.FontMetrics;
28 import android.graphics.drawable.GradientDrawable;
29 import android.util.AttributeSet;
30 import android.view.Gravity;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.widget.TextView;
34 
35 import androidx.annotation.ColorInt;
36 import androidx.annotation.IntDef;
37 import androidx.core.text.TextUtilsCompat;
38 import androidx.preference.Preference;
39 import androidx.preference.PreferenceViewHolder;
40 
41 import com.android.settings.R;
42 
43 import com.google.common.primitives.Floats;
44 import com.google.common.primitives.Ints;
45 
46 import java.lang.annotation.Retention;
47 import java.lang.annotation.RetentionPolicy;
48 import java.util.ArrayList;
49 import java.util.Arrays;
50 import java.util.Collections;
51 import java.util.Comparator;
52 import java.util.List;
53 import java.util.Locale;
54 import java.util.stream.Collectors;
55 
56 /** Preference that easier preview by matching name to color. */
57 public final class PaletteListPreference extends Preference {
58 
59     private final List<Integer> mGradientColors = new ArrayList<>();
60     private final List<Float> mGradientOffsets = new ArrayList<>();
61 
62     @IntDef({
63             Position.START,
64             Position.CENTER,
65             Position.END,
66     })
67     @Retention(RetentionPolicy.SOURCE)
68     @interface Position {
69         int START = 0;
70         int CENTER = 1;
71         int END = 2;
72     }
73 
74     /**
75      * Constructs a new PaletteListPreference with the given context's theme and the supplied
76      * attribute set.
77      *
78      * @param context The Context this is associated with, through which it can access the current
79      *                theme, resources, etc.
80      * @param attrs The attributes of the XML tag that is inflating the view.
81      */
PaletteListPreference(Context context, AttributeSet attrs)82     public PaletteListPreference(Context context, AttributeSet attrs) {
83         this(context, attrs, 0);
84     }
85 
86     /**
87      * Constructs a new PaletteListPreference with the given context's theme, the supplied
88      * attribute set, and default style attribute.
89      *
90      * @param context The Context this is associated with, through which it can access the
91      *                current theme, resources, etc.
92      * @param attrs The attributes of the XML tag that is inflating the view.
93      * @param defStyleAttr An attribute in the current theme that contains a reference to a style
94      *                     resource that supplies default
95      *                     values for the view. Can be 0 to not look for
96      *                     defaults.
97      */
PaletteListPreference(Context context, AttributeSet attrs, int defStyleAttr)98     public PaletteListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
99         super(context, attrs, defStyleAttr);
100         setLayoutResource(R.layout.daltonizer_preview);
101     }
102 
103     @Override
onBindViewHolder(PreferenceViewHolder holder)104     public void onBindViewHolder(PreferenceViewHolder holder) {
105         super.onBindViewHolder(holder);
106 
107         final ViewGroup paletteView = holder.itemView.findViewById(R.id.palette_view);
108         initPaletteAttributes(getContext());
109         initPaletteView(getContext(), paletteView);
110     }
111 
initPaletteAttributes(Context context)112     private void initPaletteAttributes(Context context) {
113         final int defaultColor = context.getColor(R.color.palette_list_gradient_background);
114         mGradientColors.add(Position.START, defaultColor);
115         mGradientColors.add(Position.CENTER, defaultColor);
116         mGradientColors.add(Position.END, defaultColor);
117 
118         mGradientOffsets.add(Position.START, /* element= */ 0.0f);
119         mGradientOffsets.add(Position.CENTER, /* element= */ 0.5f);
120         mGradientOffsets.add(Position.END, /* element= */ 1.0f);
121     }
122 
initPaletteView(Context context, ViewGroup rootView)123     private void initPaletteView(Context context, ViewGroup rootView) {
124         if (rootView.getChildCount() > 0) {
125             rootView.removeAllViews();
126         }
127 
128         final List<Integer> paletteColors = getPaletteColors(context);
129         final List<String> paletteData = getPaletteData(context);
130 
131         final float textPadding =
132                 context.getResources().getDimension(R.dimen.accessibility_layout_margin_start_end);
133         final String maxLengthData =
134                 Collections.max(paletteData, Comparator.comparing(String::length));
135         final int textWidth = getTextWidth(context, maxLengthData);
136         final float textBound = (textWidth + textPadding) / getScreenWidthPixels(context);
137         mGradientOffsets.set(Position.CENTER, textBound);
138 
139         final int screenHalfHeight = getScreenHeightPixels(context) / 2;
140         final int paletteItemHeight =
141                 max(screenHalfHeight / paletteData.size(), getTextLineHeight(context));
142 
143         for (int i = 0; i < paletteData.size(); ++i) {
144             final TextView textView = new TextView(context);
145             textView.setText(paletteData.get(i));
146             textView.setHeight(paletteItemHeight);
147             textView.setPaddingRelative(Math.round(textPadding), 0, 0, 0);
148             textView.setGravity(Gravity.CENTER_VERTICAL);
149             textView.setBackground(createGradientDrawable(rootView, paletteColors.get(i)));
150 
151             rootView.addView(textView);
152         }
153 
154         updateFirstAndLastItemsBackground(context, rootView, paletteData.size());
155     }
156 
createGradientDrawable(ViewGroup rootView, @ColorInt int color)157     private GradientDrawable createGradientDrawable(ViewGroup rootView, @ColorInt int color) {
158         mGradientColors.set(Position.END, color);
159 
160         final GradientDrawable gradientDrawable = new GradientDrawable();
161         final Locale locale = Locale.getDefault();
162         final Orientation orientation =
163                 TextUtilsCompat.getLayoutDirectionFromLocale(locale) == View.LAYOUT_DIRECTION_RTL
164                     ? Orientation.RIGHT_LEFT
165                     : Orientation.LEFT_RIGHT;
166         gradientDrawable.setOrientation(orientation);
167         gradientDrawable.setColors(Ints.toArray(mGradientColors), Floats.toArray(mGradientOffsets));
168 
169         return gradientDrawable;
170     }
171 
updateFirstAndLastItemsBackground(Context context, ViewGroup rootView, int size)172     private void updateFirstAndLastItemsBackground(Context context, ViewGroup rootView, int size) {
173         final int radius =
174                 context.getResources().getDimensionPixelSize(
175                         R.dimen.accessibility_illustration_view_radius);
176         final int lastIndex = size - 1;
177         final GradientDrawable firstItem =
178                 (GradientDrawable) rootView.getChildAt(0).getBackground();
179         final GradientDrawable lastItem =
180                 (GradientDrawable) rootView.getChildAt(lastIndex).getBackground();
181         firstItem.setCornerRadii(new float[]{radius, radius, radius, radius, 0, 0, 0, 0});
182         lastItem.setCornerRadii(new float[]{0, 0, 0, 0, radius, radius, radius, radius});
183     }
184 
getPaletteColors(Context context)185     private List<Integer> getPaletteColors(Context context) {
186         final int[] paletteResources =
187                 context.getResources().getIntArray(R.array.setting_palette_colors);
188         return Arrays.stream(paletteResources).boxed().collect(Collectors.toList());
189     }
190 
getPaletteData(Context context)191     private List<String> getPaletteData(Context context) {
192         final String[] paletteResources =
193                 context.getResources().getStringArray(R.array.setting_palette_data);
194         return Arrays.asList(paletteResources);
195     }
196 
getTextWidth(Context context, String text)197     private int getTextWidth(Context context, String text) {
198         final TextView tempView = new TextView(context);
199         return Math.round(tempView.getPaint().measureText(text));
200     }
201 
getTextLineHeight(Context context)202     private int getTextLineHeight(Context context) {
203         final TextView tempView = new TextView(context);
204         final FontMetrics fontMetrics = tempView.getPaint().getFontMetrics();
205         return Math.round(fontMetrics.bottom - fontMetrics.top);
206     }
207 }
208