1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 package com.android.settings.display;
15 
16 import static android.hardware.display.ColorDisplayManager.COLOR_MODE_AUTOMATIC;
17 import static android.hardware.display.ColorDisplayManager.COLOR_MODE_BOOSTED;
18 import static android.hardware.display.ColorDisplayManager.COLOR_MODE_NATURAL;
19 import static android.hardware.display.ColorDisplayManager.COLOR_MODE_SATURATED;
20 import static android.hardware.display.ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MAX;
21 import static android.hardware.display.ColorDisplayManager.VENDOR_COLOR_MODE_RANGE_MIN;
22 
23 import android.app.settings.SettingsEnums;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.res.Resources;
27 import android.database.ContentObserver;
28 import android.graphics.drawable.Drawable;
29 import android.hardware.display.ColorDisplayManager;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.Looper;
34 import android.provider.Settings.Secure;
35 import android.view.View;
36 import android.view.ViewGroup;
37 import android.widget.ImageView;
38 
39 import androidx.annotation.VisibleForTesting;
40 import androidx.preference.PreferenceScreen;
41 import androidx.viewpager.widget.PagerAdapter;
42 import androidx.viewpager.widget.ViewPager;
43 
44 import com.android.settings.R;
45 import com.android.settings.search.BaseSearchIndexProvider;
46 import com.android.settings.widget.RadioButtonPickerFragment;
47 import com.android.settingslib.search.SearchIndexable;
48 import com.android.settingslib.widget.CandidateInfo;
49 import com.android.settingslib.widget.LayoutPreference;
50 
51 import java.util.ArrayList;
52 import java.util.Arrays;
53 import java.util.List;
54 import java.util.Map;
55 
56 @SuppressWarnings("WeakerAccess")
57 @SearchIndexable
58 public class ColorModePreferenceFragment extends RadioButtonPickerFragment {
59 
60     private static final String KEY_COLOR_MODE_PREFIX = "color_mode_";
61 
62     private static final int COLOR_MODE_FALLBACK = COLOR_MODE_NATURAL;
63 
64     static final String PAGE_VIEWER_SELECTION_INDEX = "page_viewer_selection_index";
65 
66     private static final int DOT_INDICATOR_SIZE = 12;
67     private static final int DOT_INDICATOR_LEFT_PADDING = 6;
68     private static final int DOT_INDICATOR_RIGHT_PADDING = 6;
69 
70     private ContentObserver mContentObserver;
71     private ColorDisplayManager mColorDisplayManager;
72     private Resources mResources;
73 
74     private View mViewArrowPrevious;
75     private View mViewArrowNext;
76     private ViewPager mViewPager;
77 
78     private ArrayList<View> mPageList;
79 
80     private ImageView[] mDotIndicators;
81     private View[] mViewPagerImages;
82 
83     @Override
onAttach(Context context)84     public void onAttach(Context context) {
85         super.onAttach(context);
86 
87         mColorDisplayManager = context.getSystemService(ColorDisplayManager.class);
88         mResources = context.getResources();
89 
90         final ContentResolver cr = context.getContentResolver();
91         mContentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
92             @Override
93             public void onChange(boolean selfChange, Uri uri) {
94                 super.onChange(selfChange, uri);
95                 if (ColorDisplayManager.areAccessibilityTransformsEnabled(getContext())) {
96                     // Color modes are not configurable when Accessibility transforms are enabled.
97                     // Close this fragment in that case.
98                     getActivity().finish();
99                 }
100             }
101         };
102         cr.registerContentObserver(
103                 Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED),
104                 false /* notifyForDescendants */, mContentObserver, mUserId);
105         cr.registerContentObserver(
106                 Secure.getUriFor(Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED),
107                 false /* notifyForDescendants */, mContentObserver, mUserId);
108     }
109 
110     @Override
onCreate(Bundle savedInstanceState)111     public void onCreate(Bundle savedInstanceState) {
112         super.onCreate(savedInstanceState);
113         if (savedInstanceState != null) {
114             final int selectedPosition = savedInstanceState.getInt(PAGE_VIEWER_SELECTION_INDEX);
115             mViewPager.setCurrentItem(selectedPosition);
116             updateIndicator(selectedPosition);
117         }
118     }
119 
120     @Override
onDetach()121     public void onDetach() {
122         if (mContentObserver != null) {
123             getContext().getContentResolver().unregisterContentObserver(mContentObserver);
124             mContentObserver = null;
125         }
126         super.onDetach();
127     }
128 
129     @Override
onSaveInstanceState(Bundle outState)130     public void onSaveInstanceState(Bundle outState){
131         super.onSaveInstanceState(outState);
132         outState.putInt(PAGE_VIEWER_SELECTION_INDEX, mViewPager.getCurrentItem());
133     }
134 
135     @Override
getPreferenceScreenResId()136     protected int getPreferenceScreenResId() {
137         return R.xml.color_mode_settings;
138     }
139 
140     @VisibleForTesting
configureAndInstallPreview(LayoutPreference preview, PreferenceScreen screen)141     void configureAndInstallPreview(LayoutPreference preview, PreferenceScreen screen) {
142         preview.setSelectable(false);
143         screen.addPreference(preview);
144     }
145 
146     @VisibleForTesting
getViewPagerResource()147     public ArrayList<Integer> getViewPagerResource() {
148         return new ArrayList<Integer>(
149                 Arrays.asList(
150                         R.layout.color_mode_view1,
151                         R.layout.color_mode_view2,
152                         R.layout.color_mode_view3));
153     }
154 
addViewPager(LayoutPreference preview)155     void addViewPager(LayoutPreference preview) {
156         final ArrayList<Integer> tmpviewPagerList = getViewPagerResource();
157         mViewPager = preview.findViewById(R.id.viewpager);
158 
159         mViewPagerImages = new View[3];
160         for (int idx = 0; idx < tmpviewPagerList.size(); idx++) {
161             mViewPagerImages[idx] =
162                     getLayoutInflater().inflate(tmpviewPagerList.get(idx), null /* root */);
163         }
164 
165         mPageList = new ArrayList<View>();
166         mPageList.add(mViewPagerImages[0]);
167         mPageList.add(mViewPagerImages[1]);
168         mPageList.add(mViewPagerImages[2]);
169 
170         mViewPager.setAdapter(new ColorPagerAdapter(mPageList));
171 
172         mViewArrowPrevious = preview.findViewById(R.id.arrow_previous);
173         mViewArrowPrevious.setOnClickListener(v -> {
174             final int previousPos = mViewPager.getCurrentItem() - 1;
175             mViewPager.setCurrentItem(previousPos, true);
176         });
177 
178         mViewArrowNext = preview.findViewById(R.id.arrow_next);
179         mViewArrowNext.setOnClickListener(v -> {
180             final int nextPos = mViewPager.getCurrentItem() + 1;
181             mViewPager.setCurrentItem(nextPos, true);
182         });
183 
184         mViewPager.addOnPageChangeListener(createPageListener());
185 
186         final ViewGroup viewGroup = (ViewGroup) preview.findViewById(R.id.viewGroup);
187         mDotIndicators = new ImageView[mPageList.size()];
188         for (int i = 0; i < mPageList.size(); i++) {
189             final ImageView imageView = new ImageView(getContext());
190             final ViewGroup.MarginLayoutParams lp =
191                     new ViewGroup.MarginLayoutParams(DOT_INDICATOR_SIZE, DOT_INDICATOR_SIZE);
192             lp.setMargins(DOT_INDICATOR_LEFT_PADDING, 0, DOT_INDICATOR_RIGHT_PADDING, 0);
193             imageView.setLayoutParams(lp);
194             mDotIndicators[i] = imageView;
195 
196             viewGroup.addView(mDotIndicators[i]);
197         }
198 
199         updateIndicator(mViewPager.getCurrentItem());
200     }
201 
202     @Override
addStaticPreferences(PreferenceScreen screen)203     protected void addStaticPreferences(PreferenceScreen screen) {
204         final LayoutPreference preview = new LayoutPreference(screen.getContext(),
205                 R.layout.color_mode_preview);
206         configureAndInstallPreview(preview, screen);
207 
208         addViewPager(preview);
209     }
210 
211     @Override
getCandidates()212     protected List<? extends CandidateInfo> getCandidates() {
213         final Map<Integer, String> colorModesToSummaries =
214                 ColorModeUtils.getColorModeMapping(mResources);
215         final List<ColorModeCandidateInfo> candidates = new ArrayList<>();
216         for (int colorMode : mResources.getIntArray(
217                 com.android.internal.R.array.config_availableColorModes)) {
218             candidates.add(new ColorModeCandidateInfo(
219                     colorModesToSummaries.get(colorMode),
220                     getKeyForColorMode(colorMode),
221                     true /* enabled */));
222         }
223         return candidates;
224     }
225 
226     @Override
getDefaultKey()227     protected String getDefaultKey() {
228         final int colorMode = getColorMode();
229         if (isValidColorMode(colorMode)) {
230             return getKeyForColorMode(colorMode);
231         }
232         return getKeyForColorMode(COLOR_MODE_FALLBACK);
233     }
234 
235     @Override
setDefaultKey(String key)236     protected boolean setDefaultKey(String key) {
237         int colorMode = Integer.parseInt(key.substring(key.lastIndexOf("_") + 1));
238         if (isValidColorMode(colorMode)) {
239             setColorMode(colorMode);
240         }
241         return true;
242     }
243 
244     /**
245      * Wraps ColorDisplayManager#getColorMode for substitution in testing.
246      */
247     @VisibleForTesting
getColorMode()248     public int getColorMode() {
249         return mColorDisplayManager.getColorMode();
250     }
251 
252     /**
253      * Wraps ColorDisplayManager#setColorMode for substitution in testing.
254      */
255     @VisibleForTesting
setColorMode(int colorMode)256     public void setColorMode(int colorMode) {
257         mColorDisplayManager.setColorMode(colorMode);
258     }
259 
260     @Override
getMetricsCategory()261     public int getMetricsCategory() {
262         return SettingsEnums.COLOR_MODE_SETTINGS;
263     }
264 
265     @VisibleForTesting
getKeyForColorMode(int colorMode)266     String getKeyForColorMode(int colorMode) {
267         return KEY_COLOR_MODE_PREFIX + colorMode;
268     }
269 
isValidColorMode(int colorMode)270     private boolean isValidColorMode(int colorMode) {
271         return colorMode == COLOR_MODE_NATURAL
272                 || colorMode == COLOR_MODE_BOOSTED
273                 || colorMode == COLOR_MODE_SATURATED
274                 || colorMode == COLOR_MODE_AUTOMATIC
275                 || (colorMode >= VENDOR_COLOR_MODE_RANGE_MIN
276                 && colorMode <= VENDOR_COLOR_MODE_RANGE_MAX);
277     }
278 
279     @VisibleForTesting
280     static class ColorModeCandidateInfo extends CandidateInfo {
281         private final CharSequence mLabel;
282         private final String mKey;
283 
ColorModeCandidateInfo(CharSequence label, String key, boolean enabled)284         ColorModeCandidateInfo(CharSequence label, String key, boolean enabled) {
285             super(enabled);
286             mLabel = label;
287             mKey = key;
288         }
289 
290         @Override
loadLabel()291         public CharSequence loadLabel() {
292             return mLabel;
293         }
294 
295         @Override
loadIcon()296         public Drawable loadIcon() {
297             return null;
298         }
299 
300         @Override
getKey()301         public String getKey() {
302             return mKey;
303         }
304     }
305 
createPageListener()306     private ViewPager.OnPageChangeListener createPageListener() {
307         return new ViewPager.OnPageChangeListener() {
308             @Override
309             public void onPageScrolled(
310                     int position, float positionOffset, int positionOffsetPixels) {
311                 if (positionOffset != 0) {
312                     for (int idx = 0; idx < mPageList.size(); idx++) {
313                         mViewPagerImages[idx].setVisibility(View.VISIBLE);
314                     }
315                 } else {
316                     mViewPagerImages[position].setContentDescription(
317                             getContext().getString(R.string.colors_viewpager_content_description));
318                     updateIndicator(position);
319                 }
320             }
321 
322             @Override
323             public void onPageSelected(int position) {}
324 
325             @Override
326             public void onPageScrollStateChanged(int state) {}
327         };
328     }
329 
330     private void updateIndicator(int position) {
331         for (int i = 0; i < mPageList.size(); i++) {
332             if (position == i) {
333                 mDotIndicators[i].setBackgroundResource(
334                         R.drawable.ic_color_page_indicator_focused);
335 
336                 mViewPagerImages[i].setVisibility(View.VISIBLE);
337             } else {
338                 mDotIndicators[i].setBackgroundResource(
339                         R.drawable.ic_color_page_indicator_unfocused);
340 
341                 mViewPagerImages[i].setVisibility(View.INVISIBLE);
342             }
343         }
344 
345         if (position == 0) {
346             mViewArrowPrevious.setVisibility(View.INVISIBLE);
347             mViewArrowNext.setVisibility(View.VISIBLE);
348         } else if (position == (mPageList.size() - 1)) {
349             mViewArrowPrevious.setVisibility(View.VISIBLE);
350             mViewArrowNext.setVisibility(View.INVISIBLE);
351         } else {
352             mViewArrowPrevious.setVisibility(View.VISIBLE);
353             mViewArrowNext.setVisibility(View.VISIBLE);
354         }
355     }
356 
357     static class ColorPagerAdapter extends PagerAdapter {
358         private final ArrayList<View> mPageViewList;
359 
360         ColorPagerAdapter(ArrayList<View> pageViewList) {
361             mPageViewList = pageViewList;
362         }
363 
364         @Override
365         public void destroyItem(ViewGroup container, int position, Object object) {
366             if (mPageViewList.get(position) != null) {
367                 container.removeView(mPageViewList.get(position));
368             }
369         }
370 
371         @Override
372         public Object instantiateItem(ViewGroup container, int position) {
373             container.addView(mPageViewList.get(position));
374             return mPageViewList.get(position);
375         }
376 
377         @Override
378         public int getCount() {
379             return mPageViewList.size();
380         }
381 
382         @Override
383         public boolean isViewFromObject(View view, Object object) {
384             return object == view;
385         }
386     }
387 
388     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
389             new BaseSearchIndexProvider(R.xml.color_mode_settings) {
390 
391                 @Override
392                 protected boolean isPageSearchEnabled(Context context) {
393                     final int[] availableColorModes = context.getResources().getIntArray(
394                             com.android.internal.R.array.config_availableColorModes);
395                     return availableColorModes != null && availableColorModes.length > 0
396                             && !ColorDisplayManager.areAccessibilityTransformsEnabled(context);
397                 }
398             };
399 }
400