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