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.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 getMetricsCategory()100 protected int getMetricsCategory() { 101 return MetricsEvent.ACCESSIBILITY_CAPTION_PROPERTIES; 102 } 103 104 @Override onCreate(Bundle icicle)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 onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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 onViewCreated(View view, Bundle savedInstanceState)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 onActivityCreated(Bundle savedInstanceState)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.setCheckedInternal(enabled); 162 mToggleSwitch = mSwitchBar.getSwitch(); 163 164 getPreferenceScreen().setEnabled(enabled); 165 166 refreshPreviewText(); 167 168 installSwitchBarToggleSwitch(); 169 } 170 171 @Override onDestroyView()172 public void onDestroyView() { 173 super.onDestroyView(); 174 removeSwitchBarToggleSwitch(); 175 } 176 refreshPreviewText()177 private void refreshPreviewText() { 178 final Context context = getActivity(); 179 if (context == null) { 180 // We've been destroyed, abort! 181 return; 182 } 183 184 final SubtitleView preview = mPreviewText; 185 if (preview != null) { 186 final int styleId = mCaptioningManager.getRawUserStyle(); 187 applyCaptionProperties(mCaptioningManager, preview, mPreviewViewport, styleId); 188 189 final Locale locale = mCaptioningManager.getLocale(); 190 if (locale != null) { 191 final CharSequence localizedText = AccessibilityUtils.getTextForLocale( 192 context, locale, R.string.captioning_preview_text); 193 preview.setText(localizedText); 194 } else { 195 preview.setText(R.string.captioning_preview_text); 196 } 197 198 final CaptionStyle style = mCaptioningManager.getUserStyle(); 199 if (style.hasWindowColor()) { 200 mPreviewWindow.setBackgroundColor(style.windowColor); 201 } else { 202 final CaptionStyle defStyle = CaptionStyle.DEFAULT; 203 mPreviewWindow.setBackgroundColor(defStyle.windowColor); 204 } 205 } 206 } 207 applyCaptionProperties(CaptioningManager manager, SubtitleView previewText, View previewWindow, int styleId)208 public static void applyCaptionProperties(CaptioningManager manager, SubtitleView previewText, 209 View previewWindow, int styleId) { 210 previewText.setStyle(styleId); 211 212 final Context context = previewText.getContext(); 213 final ContentResolver cr = context.getContentResolver(); 214 final float fontScale = manager.getFontScale(); 215 if (previewWindow != null) { 216 // Assume the viewport is clipped with a 16:9 aspect ratio. 217 final float virtualHeight = Math.max(9 * previewWindow.getWidth(), 218 16 * previewWindow.getHeight()) / 16.0f; 219 previewText.setTextSize(virtualHeight * LINE_HEIGHT_RATIO * fontScale); 220 } else { 221 final float textSize = context.getResources().getDimension( 222 R.dimen.caption_preview_text_size); 223 previewText.setTextSize(textSize * fontScale); 224 } 225 226 final Locale locale = manager.getLocale(); 227 if (locale != null) { 228 final CharSequence localizedText = AccessibilityUtils.getTextForLocale( 229 context, locale, R.string.captioning_preview_characters); 230 previewText.setText(localizedText); 231 } else { 232 previewText.setText(R.string.captioning_preview_characters); 233 } 234 } 235 onInstallSwitchBarToggleSwitch()236 protected void onInstallSwitchBarToggleSwitch() { 237 mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() { 238 @Override 239 public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) { 240 mSwitchBar.setCheckedInternal(checked); 241 Settings.Secure.putInt(getActivity().getContentResolver(), 242 Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, checked ? 1 : 0); 243 getPreferenceScreen().setEnabled(checked); 244 if (mPreviewText != null) { 245 mPreviewText.setVisibility(checked ? View.VISIBLE : View.INVISIBLE); 246 } 247 return false; 248 } 249 }); 250 } 251 installSwitchBarToggleSwitch()252 private void installSwitchBarToggleSwitch() { 253 onInstallSwitchBarToggleSwitch(); 254 mSwitchBar.show(); 255 } 256 removeSwitchBarToggleSwitch()257 private void removeSwitchBarToggleSwitch() { 258 mSwitchBar.hide(); 259 mToggleSwitch.setOnBeforeCheckedChangeListener(null); 260 } 261 initializeAllPreferences()262 private void initializeAllPreferences() { 263 mLocale = (LocalePreference) findPreference(PREF_LOCALE); 264 mFontSize = (ListPreference) findPreference(PREF_FONT_SIZE); 265 266 final Resources res = getResources(); 267 final int[] presetValues = res.getIntArray(R.array.captioning_preset_selector_values); 268 final String[] presetTitles = res.getStringArray(R.array.captioning_preset_selector_titles); 269 mPreset = (PresetPreference) findPreference(PREF_PRESET); 270 mPreset.setValues(presetValues); 271 mPreset.setTitles(presetTitles); 272 273 mCustom = (PreferenceCategory) findPreference(PREF_CUSTOM); 274 mShowingCustom = true; 275 276 final int[] colorValues = res.getIntArray(R.array.captioning_color_selector_values); 277 final String[] colorTitles = res.getStringArray(R.array.captioning_color_selector_titles); 278 mForegroundColor = (ColorPreference) mCustom.findPreference(PREF_FOREGROUND_COLOR); 279 mForegroundColor.setTitles(colorTitles); 280 mForegroundColor.setValues(colorValues); 281 282 final int[] opacityValues = res.getIntArray(R.array.captioning_opacity_selector_values); 283 final String[] opacityTitles = res.getStringArray( 284 R.array.captioning_opacity_selector_titles); 285 mForegroundOpacity = (ColorPreference) mCustom.findPreference(PREF_FOREGROUND_OPACITY); 286 mForegroundOpacity.setTitles(opacityTitles); 287 mForegroundOpacity.setValues(opacityValues); 288 289 mEdgeColor = (ColorPreference) mCustom.findPreference(PREF_EDGE_COLOR); 290 mEdgeColor.setTitles(colorTitles); 291 mEdgeColor.setValues(colorValues); 292 293 // Add "none" as an additional option for backgrounds. 294 final int[] bgColorValues = new int[colorValues.length + 1]; 295 final String[] bgColorTitles = new String[colorTitles.length + 1]; 296 System.arraycopy(colorValues, 0, bgColorValues, 1, colorValues.length); 297 System.arraycopy(colorTitles, 0, bgColorTitles, 1, colorTitles.length); 298 bgColorValues[0] = Color.TRANSPARENT; 299 bgColorTitles[0] = getString(R.string.color_none); 300 mBackgroundColor = (ColorPreference) mCustom.findPreference(PREF_BACKGROUND_COLOR); 301 mBackgroundColor.setTitles(bgColorTitles); 302 mBackgroundColor.setValues(bgColorValues); 303 304 mBackgroundOpacity = (ColorPreference) mCustom.findPreference(PREF_BACKGROUND_OPACITY); 305 mBackgroundOpacity.setTitles(opacityTitles); 306 mBackgroundOpacity.setValues(opacityValues); 307 308 mWindowColor = (ColorPreference) mCustom.findPreference(PREF_WINDOW_COLOR); 309 mWindowColor.setTitles(bgColorTitles); 310 mWindowColor.setValues(bgColorValues); 311 312 mWindowOpacity = (ColorPreference) mCustom.findPreference(PREF_WINDOW_OPACITY); 313 mWindowOpacity.setTitles(opacityTitles); 314 mWindowOpacity.setValues(opacityValues); 315 316 mEdgeType = (EdgeTypePreference) mCustom.findPreference(PREF_EDGE_TYPE); 317 mTypeface = (ListPreference) mCustom.findPreference(PREF_TYPEFACE); 318 } 319 installUpdateListeners()320 private void installUpdateListeners() { 321 mPreset.setOnValueChangedListener(this); 322 mForegroundColor.setOnValueChangedListener(this); 323 mForegroundOpacity.setOnValueChangedListener(this); 324 mEdgeColor.setOnValueChangedListener(this); 325 mBackgroundColor.setOnValueChangedListener(this); 326 mBackgroundOpacity.setOnValueChangedListener(this); 327 mWindowColor.setOnValueChangedListener(this); 328 mWindowOpacity.setOnValueChangedListener(this); 329 mEdgeType.setOnValueChangedListener(this); 330 331 mTypeface.setOnPreferenceChangeListener(this); 332 mFontSize.setOnPreferenceChangeListener(this); 333 mLocale.setOnPreferenceChangeListener(this); 334 } 335 updateAllPreferences()336 private void updateAllPreferences() { 337 final int preset = mCaptioningManager.getRawUserStyle(); 338 mPreset.setValue(preset); 339 340 final float fontSize = mCaptioningManager.getFontScale(); 341 mFontSize.setValue(Float.toString(fontSize)); 342 343 final ContentResolver cr = getContentResolver(); 344 final CaptionStyle attrs = CaptionStyle.getCustomStyle(cr); 345 mEdgeType.setValue(attrs.edgeType); 346 mEdgeColor.setValue(attrs.edgeColor); 347 348 final int foregroundColor = attrs.hasForegroundColor() ? 349 attrs.foregroundColor : CaptionStyle.COLOR_UNSPECIFIED; 350 parseColorOpacity(mForegroundColor, mForegroundOpacity, foregroundColor); 351 352 final int backgroundColor = attrs.hasBackgroundColor() ? 353 attrs.backgroundColor : CaptionStyle.COLOR_UNSPECIFIED; 354 parseColorOpacity(mBackgroundColor, mBackgroundOpacity, backgroundColor); 355 356 final int windowColor = attrs.hasWindowColor() ? 357 attrs.windowColor : CaptionStyle.COLOR_UNSPECIFIED; 358 parseColorOpacity(mWindowColor, mWindowOpacity, windowColor); 359 360 final String rawTypeface = attrs.mRawTypeface; 361 mTypeface.setValue(rawTypeface == null ? "" : rawTypeface); 362 363 final String rawLocale = mCaptioningManager.getRawLocale(); 364 mLocale.setValue(rawLocale == null ? "" : rawLocale); 365 } 366 367 /** 368 * Unpack the specified color value and update the preferences. 369 * 370 * @param color color preference 371 * @param opacity opacity preference 372 * @param value packed value 373 */ parseColorOpacity(ColorPreference color, ColorPreference opacity, int value)374 private void parseColorOpacity(ColorPreference color, ColorPreference opacity, int value) { 375 final int colorValue; 376 final int opacityValue; 377 if (!CaptionStyle.hasColor(value)) { 378 // "Default" color with variable alpha. 379 colorValue = CaptionStyle.COLOR_UNSPECIFIED; 380 opacityValue = (value & 0xFF) << 24; 381 } else if ((value >>> 24) == 0) { 382 // "None" color with variable alpha. 383 colorValue = Color.TRANSPARENT; 384 opacityValue = (value & 0xFF) << 24; 385 } else { 386 // Normal color. 387 colorValue = value | 0xFF000000; 388 opacityValue = value & 0xFF000000; 389 } 390 391 // Opacity value is always white. 392 opacity.setValue(opacityValue | 0xFFFFFF); 393 color.setValue(colorValue); 394 } 395 mergeColorOpacity(ColorPreference color, ColorPreference opacity)396 private int mergeColorOpacity(ColorPreference color, ColorPreference opacity) { 397 final int colorValue = color.getValue(); 398 final int opacityValue = opacity.getValue(); 399 final int value; 400 // "Default" is 0x00FFFFFF or, for legacy support, 0x00000100. 401 if (!CaptionStyle.hasColor(colorValue)) { 402 // Encode "default" as 0x00FFFFaa. 403 value = 0x00FFFF00 | Color.alpha(opacityValue); 404 } else if (colorValue == Color.TRANSPARENT) { 405 // Encode "none" as 0x000000aa. 406 value = Color.alpha(opacityValue); 407 } else { 408 // Encode custom color normally. 409 value = colorValue & 0x00FFFFFF | opacityValue & 0xFF000000; 410 } 411 return value; 412 } 413 refreshShowingCustom()414 private void refreshShowingCustom() { 415 final boolean customPreset = mPreset.getValue() == CaptionStyle.PRESET_CUSTOM; 416 if (!customPreset && mShowingCustom) { 417 getPreferenceScreen().removePreference(mCustom); 418 mShowingCustom = false; 419 } else if (customPreset && !mShowingCustom) { 420 getPreferenceScreen().addPreference(mCustom); 421 mShowingCustom = true; 422 } 423 } 424 425 @Override onValueChanged(ListDialogPreference preference, int value)426 public void onValueChanged(ListDialogPreference preference, int value) { 427 final ContentResolver cr = getActivity().getContentResolver(); 428 if (mForegroundColor == preference || mForegroundOpacity == preference) { 429 final int merged = mergeColorOpacity(mForegroundColor, mForegroundOpacity); 430 Settings.Secure.putInt( 431 cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, merged); 432 } else if (mBackgroundColor == preference || mBackgroundOpacity == preference) { 433 final int merged = mergeColorOpacity(mBackgroundColor, mBackgroundOpacity); 434 Settings.Secure.putInt( 435 cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, merged); 436 } else if (mWindowColor == preference || mWindowOpacity == preference) { 437 final int merged = mergeColorOpacity(mWindowColor, mWindowOpacity); 438 Settings.Secure.putInt( 439 cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, merged); 440 } else if (mEdgeColor == preference) { 441 Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, value); 442 } else if (mPreset == preference) { 443 Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET, value); 444 refreshShowingCustom(); 445 } else if (mEdgeType == preference) { 446 Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, value); 447 } 448 449 refreshPreviewText(); 450 } 451 452 @Override onPreferenceChange(Preference preference, Object value)453 public boolean onPreferenceChange(Preference preference, Object value) { 454 final ContentResolver cr = getActivity().getContentResolver(); 455 if (mTypeface == preference) { 456 Settings.Secure.putString( 457 cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE, (String) value); 458 } else if (mFontSize == preference) { 459 Settings.Secure.putFloat( 460 cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE, 461 Float.parseFloat((String) value)); 462 } else if (mLocale == preference) { 463 Settings.Secure.putString( 464 cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE, (String) value); 465 } 466 467 refreshPreviewText(); 468 return true; 469 } 470 } 471