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