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