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