1 /* 2 * Copyright (C) 2013 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 android.app.settings.SettingsEnums; 20 import android.content.ContentResolver; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.graphics.Color; 24 import android.os.Bundle; 25 import android.provider.Settings; 26 import android.view.View; 27 import android.view.accessibility.CaptioningManager; 28 import android.view.accessibility.CaptioningManager.CaptionStyle; 29 30 import androidx.preference.ListPreference; 31 import androidx.preference.Preference; 32 import androidx.preference.Preference.OnPreferenceChangeListener; 33 import androidx.preference.PreferenceCategory; 34 import androidx.preference.SwitchPreference; 35 36 import com.android.internal.widget.SubtitleView; 37 import com.android.settings.R; 38 import com.android.settings.SettingsPreferenceFragment; 39 import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener; 40 import com.android.settingslib.accessibility.AccessibilityUtils; 41 import com.android.settingslib.widget.LayoutPreference; 42 43 import java.util.ArrayList; 44 import java.util.List; 45 import java.util.Locale; 46 47 /** 48 * Settings fragment containing captioning properties. 49 */ 50 public class CaptionPropertiesFragment extends SettingsPreferenceFragment 51 implements OnPreferenceChangeListener, OnValueChangedListener { 52 private static final String PREF_CAPTION_PREVIEW = "caption_preview"; 53 private static final String PREF_BACKGROUND_COLOR = "captioning_background_color"; 54 private static final String PREF_BACKGROUND_OPACITY = "captioning_background_opacity"; 55 private static final String PREF_FOREGROUND_COLOR = "captioning_foreground_color"; 56 private static final String PREF_FOREGROUND_OPACITY = "captioning_foreground_opacity"; 57 private static final String PREF_WINDOW_COLOR = "captioning_window_color"; 58 private static final String PREF_WINDOW_OPACITY = "captioning_window_opacity"; 59 private static final String PREF_EDGE_COLOR = "captioning_edge_color"; 60 private static final String PREF_EDGE_TYPE = "captioning_edge_type"; 61 private static final String PREF_FONT_SIZE = "captioning_font_size"; 62 private static final String PREF_TYPEFACE = "captioning_typeface"; 63 private static final String PREF_LOCALE = "captioning_locale"; 64 private static final String PREF_PRESET = "captioning_preset"; 65 private static final String PREF_SWITCH = "captioning_preference_switch"; 66 private static final String PREF_CUSTOM = "custom"; 67 68 /** WebVtt specifies line height as 5.3% of the viewport height. */ 69 private static final float LINE_HEIGHT_RATIO = 0.0533f; 70 71 private CaptioningManager mCaptioningManager; 72 private SubtitleView mPreviewText; 73 private View mPreviewWindow; 74 private View mPreviewViewport; 75 76 // Standard options. 77 private SwitchPreference mSwitch; 78 private LocalePreference mLocale; 79 private ListPreference mFontSize; 80 private PresetPreference mPreset; 81 82 // Custom options. 83 private ListPreference mTypeface; 84 private ColorPreference mForegroundColor; 85 private ColorPreference mForegroundOpacity; 86 private EdgeTypePreference mEdgeType; 87 private ColorPreference mEdgeColor; 88 private ColorPreference mBackgroundColor; 89 private ColorPreference mBackgroundOpacity; 90 private ColorPreference mWindowColor; 91 private ColorPreference mWindowOpacity; 92 private PreferenceCategory mCustom; 93 94 private boolean mShowingCustom; 95 96 private final List<Preference> mPreferenceList = new ArrayList<>(); 97 98 private final View.OnLayoutChangeListener mLayoutChangeListener = 99 new View.OnLayoutChangeListener() { 100 @Override 101 public void onLayoutChange(View v, int left, int top, int right, int bottom, 102 int oldLeft, int oldTop, int oldRight, int oldBottom) { 103 // Remove the listener once the callback is triggered. 104 mPreviewViewport.removeOnLayoutChangeListener(this); 105 refreshPreviewText(); 106 } 107 }; 108 109 @Override getMetricsCategory()110 public int getMetricsCategory() { 111 return SettingsEnums.ACCESSIBILITY_CAPTION_PROPERTIES; 112 } 113 114 @Override onCreate(Bundle icicle)115 public void onCreate(Bundle icicle) { 116 super.onCreate(icicle); 117 118 mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE); 119 120 addPreferencesFromResource(R.xml.captioning_settings); 121 initializeAllPreferences(); 122 updateAllPreferences(); 123 refreshShowingCustom(); 124 installUpdateListeners(); 125 refreshPreviewText(); 126 } 127 setPreferenceViewEnabled(boolean enabled)128 private void setPreferenceViewEnabled(boolean enabled) { 129 for (Preference preference : mPreferenceList) { 130 preference.setEnabled(enabled); 131 } 132 } 133 refreshPreferenceViewEnabled(boolean enabled)134 private void refreshPreferenceViewEnabled(boolean enabled) { 135 setPreferenceViewEnabled(enabled); 136 mPreviewText.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE); 137 } 138 refreshPreviewText()139 private void refreshPreviewText() { 140 final Context context = getActivity(); 141 if (context == null) { 142 // We've been destroyed, abort! 143 return; 144 } 145 146 final SubtitleView preview = mPreviewText; 147 if (preview != null) { 148 final int styleId = mCaptioningManager.getRawUserStyle(); 149 applyCaptionProperties(mCaptioningManager, preview, mPreviewViewport, styleId); 150 151 final Locale locale = mCaptioningManager.getLocale(); 152 if (locale != null) { 153 final CharSequence localizedText = AccessibilityUtils.getTextForLocale( 154 context, locale, R.string.captioning_preview_text); 155 preview.setText(localizedText); 156 } else { 157 preview.setText(R.string.captioning_preview_text); 158 } 159 160 final CaptionStyle style = mCaptioningManager.getUserStyle(); 161 if (style.hasWindowColor()) { 162 mPreviewWindow.setBackgroundColor(style.windowColor); 163 } else { 164 final CaptionStyle defStyle = CaptionStyle.DEFAULT; 165 mPreviewWindow.setBackgroundColor(defStyle.windowColor); 166 } 167 } 168 } 169 applyCaptionProperties(CaptioningManager manager, SubtitleView previewText, View previewWindow, int styleId)170 public static void applyCaptionProperties(CaptioningManager manager, SubtitleView previewText, 171 View previewWindow, int styleId) { 172 previewText.setStyle(styleId); 173 174 final Context context = previewText.getContext(); 175 final ContentResolver cr = context.getContentResolver(); 176 final float fontScale = manager.getFontScale(); 177 if (previewWindow != null) { 178 // Assume the viewport is clipped with a 16:9 aspect ratio. 179 final float virtualHeight = Math.max(9 * previewWindow.getWidth(), 180 16 * previewWindow.getHeight()) / 16.0f; 181 previewText.setTextSize(virtualHeight * LINE_HEIGHT_RATIO * fontScale); 182 } else { 183 final float textSize = context.getResources().getDimension( 184 R.dimen.caption_preview_text_size); 185 previewText.setTextSize(textSize * fontScale); 186 } 187 188 final Locale locale = manager.getLocale(); 189 if (locale != null) { 190 final CharSequence localizedText = AccessibilityUtils.getTextForLocale( 191 context, locale, R.string.captioning_preview_characters); 192 previewText.setText(localizedText); 193 } else { 194 previewText.setText(R.string.captioning_preview_characters); 195 } 196 } 197 initializeAllPreferences()198 private void initializeAllPreferences() { 199 final LayoutPreference captionPreview = findPreference(PREF_CAPTION_PREVIEW); 200 201 mPreviewText = captionPreview.findViewById(R.id.preview_text); 202 203 mPreviewWindow = captionPreview.findViewById(R.id.preview_window); 204 205 mPreviewViewport = captionPreview.findViewById(R.id.preview_viewport); 206 mPreviewViewport.addOnLayoutChangeListener(mLayoutChangeListener); 207 208 final Resources res = getResources(); 209 final int[] presetValues = res.getIntArray(R.array.captioning_preset_selector_values); 210 final String[] presetTitles = res.getStringArray(R.array.captioning_preset_selector_titles); 211 mPreset = (PresetPreference) findPreference(PREF_PRESET); 212 mPreset.setValues(presetValues); 213 mPreset.setTitles(presetTitles); 214 215 mSwitch = (SwitchPreference) findPreference(PREF_SWITCH); 216 mLocale = (LocalePreference) findPreference(PREF_LOCALE); 217 mFontSize = (ListPreference) findPreference(PREF_FONT_SIZE); 218 219 // Initialize the preference list 220 mPreferenceList.add(mLocale); 221 mPreferenceList.add(mFontSize); 222 mPreferenceList.add(mPreset); 223 224 refreshPreferenceViewEnabled(mCaptioningManager.isEnabled()); 225 226 mCustom = (PreferenceCategory) findPreference(PREF_CUSTOM); 227 mShowingCustom = true; 228 229 final int[] colorValues = res.getIntArray(R.array.captioning_color_selector_values); 230 final String[] colorTitles = res.getStringArray(R.array.captioning_color_selector_titles); 231 mForegroundColor = (ColorPreference) mCustom.findPreference(PREF_FOREGROUND_COLOR); 232 mForegroundColor.setTitles(colorTitles); 233 mForegroundColor.setValues(colorValues); 234 235 final int[] opacityValues = res.getIntArray(R.array.captioning_opacity_selector_values); 236 final String[] opacityTitles = res.getStringArray( 237 R.array.captioning_opacity_selector_titles); 238 mForegroundOpacity = (ColorPreference) mCustom.findPreference(PREF_FOREGROUND_OPACITY); 239 mForegroundOpacity.setTitles(opacityTitles); 240 mForegroundOpacity.setValues(opacityValues); 241 242 mEdgeColor = (ColorPreference) mCustom.findPreference(PREF_EDGE_COLOR); 243 mEdgeColor.setTitles(colorTitles); 244 mEdgeColor.setValues(colorValues); 245 246 // Add "none" as an additional option for backgrounds. 247 final int[] bgColorValues = new int[colorValues.length + 1]; 248 final String[] bgColorTitles = new String[colorTitles.length + 1]; 249 System.arraycopy(colorValues, 0, bgColorValues, 1, colorValues.length); 250 System.arraycopy(colorTitles, 0, bgColorTitles, 1, colorTitles.length); 251 bgColorValues[0] = Color.TRANSPARENT; 252 bgColorTitles[0] = getString(R.string.color_none); 253 mBackgroundColor = (ColorPreference) mCustom.findPreference(PREF_BACKGROUND_COLOR); 254 mBackgroundColor.setTitles(bgColorTitles); 255 mBackgroundColor.setValues(bgColorValues); 256 257 mBackgroundOpacity = (ColorPreference) mCustom.findPreference(PREF_BACKGROUND_OPACITY); 258 mBackgroundOpacity.setTitles(opacityTitles); 259 mBackgroundOpacity.setValues(opacityValues); 260 261 mWindowColor = (ColorPreference) mCustom.findPreference(PREF_WINDOW_COLOR); 262 mWindowColor.setTitles(bgColorTitles); 263 mWindowColor.setValues(bgColorValues); 264 265 mWindowOpacity = (ColorPreference) mCustom.findPreference(PREF_WINDOW_OPACITY); 266 mWindowOpacity.setTitles(opacityTitles); 267 mWindowOpacity.setValues(opacityValues); 268 269 mEdgeType = (EdgeTypePreference) mCustom.findPreference(PREF_EDGE_TYPE); 270 mTypeface = (ListPreference) mCustom.findPreference(PREF_TYPEFACE); 271 } 272 installUpdateListeners()273 private void installUpdateListeners() { 274 mPreset.setOnValueChangedListener(this); 275 mForegroundColor.setOnValueChangedListener(this); 276 mForegroundOpacity.setOnValueChangedListener(this); 277 mEdgeColor.setOnValueChangedListener(this); 278 mBackgroundColor.setOnValueChangedListener(this); 279 mBackgroundOpacity.setOnValueChangedListener(this); 280 mWindowColor.setOnValueChangedListener(this); 281 mWindowOpacity.setOnValueChangedListener(this); 282 mEdgeType.setOnValueChangedListener(this); 283 284 mSwitch.setOnPreferenceChangeListener(this); 285 mTypeface.setOnPreferenceChangeListener(this); 286 mFontSize.setOnPreferenceChangeListener(this); 287 mLocale.setOnPreferenceChangeListener(this); 288 } 289 updateAllPreferences()290 private void updateAllPreferences() { 291 final int preset = mCaptioningManager.getRawUserStyle(); 292 mPreset.setValue(preset); 293 294 final float fontSize = mCaptioningManager.getFontScale(); 295 mFontSize.setValue(Float.toString(fontSize)); 296 297 final ContentResolver cr = getContentResolver(); 298 final CaptionStyle attrs = CaptionStyle.getCustomStyle(cr); 299 mEdgeType.setValue(attrs.edgeType); 300 mEdgeColor.setValue(attrs.edgeColor); 301 302 final int foregroundColor = attrs.hasForegroundColor() ? 303 attrs.foregroundColor : CaptionStyle.COLOR_UNSPECIFIED; 304 parseColorOpacity(mForegroundColor, mForegroundOpacity, foregroundColor); 305 306 final int backgroundColor = attrs.hasBackgroundColor() ? 307 attrs.backgroundColor : CaptionStyle.COLOR_UNSPECIFIED; 308 parseColorOpacity(mBackgroundColor, mBackgroundOpacity, backgroundColor); 309 310 final int windowColor = attrs.hasWindowColor() ? 311 attrs.windowColor : CaptionStyle.COLOR_UNSPECIFIED; 312 parseColorOpacity(mWindowColor, mWindowOpacity, windowColor); 313 314 final String rawTypeface = attrs.mRawTypeface; 315 mTypeface.setValue(rawTypeface == null ? "" : rawTypeface); 316 317 final String rawLocale = mCaptioningManager.getRawLocale(); 318 mLocale.setValue(rawLocale == null ? "" : rawLocale); 319 320 mSwitch.setChecked(mCaptioningManager.isEnabled()); 321 } 322 323 /** 324 * Unpack the specified color value and update the preferences. 325 * 326 * @param color color preference 327 * @param opacity opacity preference 328 * @param value packed value 329 */ parseColorOpacity(ColorPreference color, ColorPreference opacity, int value)330 private void parseColorOpacity(ColorPreference color, ColorPreference opacity, int value) { 331 final int colorValue; 332 final int opacityValue; 333 if (!CaptionStyle.hasColor(value)) { 334 // "Default" color with variable alpha. 335 colorValue = CaptionStyle.COLOR_UNSPECIFIED; 336 opacityValue = (value & 0xFF) << 24; 337 } else if ((value >>> 24) == 0) { 338 // "None" color with variable alpha. 339 colorValue = Color.TRANSPARENT; 340 opacityValue = (value & 0xFF) << 24; 341 } else { 342 // Normal color. 343 colorValue = value | 0xFF000000; 344 opacityValue = value & 0xFF000000; 345 } 346 347 // Opacity value is always white. 348 opacity.setValue(opacityValue | 0xFFFFFF); 349 color.setValue(colorValue); 350 } 351 mergeColorOpacity(ColorPreference color, ColorPreference opacity)352 private int mergeColorOpacity(ColorPreference color, ColorPreference opacity) { 353 final int colorValue = color.getValue(); 354 final int opacityValue = opacity.getValue(); 355 final int value; 356 // "Default" is 0x00FFFFFF or, for legacy support, 0x00000100. 357 if (!CaptionStyle.hasColor(colorValue)) { 358 // Encode "default" as 0x00FFFFaa. 359 value = 0x00FFFF00 | Color.alpha(opacityValue); 360 } else if (colorValue == Color.TRANSPARENT) { 361 // Encode "none" as 0x000000aa. 362 value = Color.alpha(opacityValue); 363 } else { 364 // Encode custom color normally. 365 value = colorValue & 0x00FFFFFF | opacityValue & 0xFF000000; 366 } 367 return value; 368 } 369 refreshShowingCustom()370 private void refreshShowingCustom() { 371 final boolean customPreset = mPreset.getValue() == CaptionStyle.PRESET_CUSTOM; 372 if (!customPreset && mShowingCustom) { 373 getPreferenceScreen().removePreference(mCustom); 374 mShowingCustom = false; 375 } else if (customPreset && !mShowingCustom) { 376 getPreferenceScreen().addPreference(mCustom); 377 mShowingCustom = true; 378 } 379 } 380 381 @Override onValueChanged(ListDialogPreference preference, int value)382 public void onValueChanged(ListDialogPreference preference, int value) { 383 final ContentResolver cr = getActivity().getContentResolver(); 384 if (mForegroundColor == preference || mForegroundOpacity == preference) { 385 final int merged = mergeColorOpacity(mForegroundColor, mForegroundOpacity); 386 Settings.Secure.putInt( 387 cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, merged); 388 } else if (mBackgroundColor == preference || mBackgroundOpacity == preference) { 389 final int merged = mergeColorOpacity(mBackgroundColor, mBackgroundOpacity); 390 Settings.Secure.putInt( 391 cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, merged); 392 } else if (mWindowColor == preference || mWindowOpacity == preference) { 393 final int merged = mergeColorOpacity(mWindowColor, mWindowOpacity); 394 Settings.Secure.putInt( 395 cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, merged); 396 } else if (mEdgeColor == preference) { 397 Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, value); 398 } else if (mPreset == preference) { 399 Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET, value); 400 refreshShowingCustom(); 401 } else if (mEdgeType == preference) { 402 Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, value); 403 } 404 405 refreshPreviewText(); 406 } 407 408 @Override onPreferenceChange(Preference preference, Object value)409 public boolean onPreferenceChange(Preference preference, Object value) { 410 final ContentResolver cr = getActivity().getContentResolver(); 411 if (mTypeface == preference) { 412 Settings.Secure.putString( 413 cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE, (String) value); 414 refreshPreviewText(); 415 } else if (mFontSize == preference) { 416 Settings.Secure.putFloat( 417 cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE, 418 Float.parseFloat((String) value)); 419 refreshPreviewText(); 420 } else if (mLocale == preference) { 421 Settings.Secure.putString( 422 cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE, (String) value); 423 refreshPreviewText(); 424 } else if (mSwitch == preference) { 425 Settings.Secure.putInt( 426 cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, (boolean) value ? 1 : 0); 427 refreshPreferenceViewEnabled((boolean) value); 428 } 429 430 return true; 431 } 432 } 433