/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.accessibility; import android.content.ContentResolver; import android.content.Context; import android.content.res.Resources; import android.graphics.Color; import android.os.Bundle; import android.preference.PreferenceFrameLayout; import android.provider.Settings; import android.support.v7.preference.ListPreference; import android.support.v7.preference.Preference; import android.support.v7.preference.Preference.OnPreferenceChangeListener; import android.support.v7.preference.PreferenceCategory; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnLayoutChangeListener; import android.view.ViewGroup; import android.view.ViewGroup.LayoutParams; import android.view.accessibility.CaptioningManager; import android.view.accessibility.CaptioningManager.CaptionStyle; import com.android.internal.logging.MetricsProto.MetricsEvent; import com.android.internal.widget.SubtitleView; import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.SettingsPreferenceFragment; import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener; import com.android.settings.widget.SwitchBar; import com.android.settings.widget.ToggleSwitch; import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener; import com.android.settingslib.accessibility.AccessibilityUtils; import java.util.Locale; /** * Settings fragment containing captioning properties. */ public class CaptionPropertiesFragment extends SettingsPreferenceFragment implements OnPreferenceChangeListener, OnValueChangedListener { private static final String PREF_BACKGROUND_COLOR = "captioning_background_color"; private static final String PREF_BACKGROUND_OPACITY = "captioning_background_opacity"; private static final String PREF_FOREGROUND_COLOR = "captioning_foreground_color"; private static final String PREF_FOREGROUND_OPACITY = "captioning_foreground_opacity"; private static final String PREF_WINDOW_COLOR = "captioning_window_color"; private static final String PREF_WINDOW_OPACITY = "captioning_window_opacity"; private static final String PREF_EDGE_COLOR = "captioning_edge_color"; private static final String PREF_EDGE_TYPE = "captioning_edge_type"; private static final String PREF_FONT_SIZE = "captioning_font_size"; private static final String PREF_TYPEFACE = "captioning_typeface"; private static final String PREF_LOCALE = "captioning_locale"; private static final String PREF_PRESET = "captioning_preset"; private static final String PREF_CUSTOM = "custom"; /** WebVtt specifies line height as 5.3% of the viewport height. */ private static final float LINE_HEIGHT_RATIO = 0.0533f; private CaptioningManager mCaptioningManager; private SubtitleView mPreviewText; private View mPreviewWindow; private View mPreviewViewport; private SwitchBar mSwitchBar; private ToggleSwitch mToggleSwitch; // Standard options. private LocalePreference mLocale; private ListPreference mFontSize; private PresetPreference mPreset; // Custom options. private ListPreference mTypeface; private ColorPreference mForegroundColor; private ColorPreference mForegroundOpacity; private EdgeTypePreference mEdgeType; private ColorPreference mEdgeColor; private ColorPreference mBackgroundColor; private ColorPreference mBackgroundOpacity; private ColorPreference mWindowColor; private ColorPreference mWindowOpacity; private PreferenceCategory mCustom; private boolean mShowingCustom; @Override protected int getMetricsCategory() { return MetricsEvent.ACCESSIBILITY_CAPTION_PROPERTIES; } @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE); addPreferencesFromResource(R.xml.captioning_settings); initializeAllPreferences(); updateAllPreferences(); refreshShowingCustom(); installUpdateListeners(); } @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View rootView = inflater.inflate(R.layout.captioning_preview, container, false); // We have to do this now because PreferenceFrameLayout looks at it // only when the view is added. if (container instanceof PreferenceFrameLayout) { ((PreferenceFrameLayout.LayoutParams) rootView.getLayoutParams()).removeBorders = true; } final View content = super.onCreateView(inflater, container, savedInstanceState); ((ViewGroup) rootView.findViewById(R.id.properties_fragment)).addView( content, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); return rootView; } @Override public void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); final boolean enabled = mCaptioningManager.isEnabled(); mPreviewText = (SubtitleView) view.findViewById(R.id.preview_text); mPreviewText.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE); mPreviewWindow = view.findViewById(R.id.preview_window); mPreviewViewport = view.findViewById(R.id.preview_viewport); mPreviewViewport.addOnLayoutChangeListener(new OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { refreshPreviewText(); } }); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); final boolean enabled = mCaptioningManager.isEnabled(); SettingsActivity activity = (SettingsActivity) getActivity(); mSwitchBar = activity.getSwitchBar(); mSwitchBar.setCheckedInternal(enabled); mToggleSwitch = mSwitchBar.getSwitch(); getPreferenceScreen().setEnabled(enabled); refreshPreviewText(); installSwitchBarToggleSwitch(); } @Override public void onDestroyView() { super.onDestroyView(); removeSwitchBarToggleSwitch(); } private void refreshPreviewText() { final Context context = getActivity(); if (context == null) { // We've been destroyed, abort! return; } final SubtitleView preview = mPreviewText; if (preview != null) { final int styleId = mCaptioningManager.getRawUserStyle(); applyCaptionProperties(mCaptioningManager, preview, mPreviewViewport, styleId); final Locale locale = mCaptioningManager.getLocale(); if (locale != null) { final CharSequence localizedText = AccessibilityUtils.getTextForLocale( context, locale, R.string.captioning_preview_text); preview.setText(localizedText); } else { preview.setText(R.string.captioning_preview_text); } final CaptionStyle style = mCaptioningManager.getUserStyle(); if (style.hasWindowColor()) { mPreviewWindow.setBackgroundColor(style.windowColor); } else { final CaptionStyle defStyle = CaptionStyle.DEFAULT; mPreviewWindow.setBackgroundColor(defStyle.windowColor); } } } public static void applyCaptionProperties(CaptioningManager manager, SubtitleView previewText, View previewWindow, int styleId) { previewText.setStyle(styleId); final Context context = previewText.getContext(); final ContentResolver cr = context.getContentResolver(); final float fontScale = manager.getFontScale(); if (previewWindow != null) { // Assume the viewport is clipped with a 16:9 aspect ratio. final float virtualHeight = Math.max(9 * previewWindow.getWidth(), 16 * previewWindow.getHeight()) / 16.0f; previewText.setTextSize(virtualHeight * LINE_HEIGHT_RATIO * fontScale); } else { final float textSize = context.getResources().getDimension( R.dimen.caption_preview_text_size); previewText.setTextSize(textSize * fontScale); } final Locale locale = manager.getLocale(); if (locale != null) { final CharSequence localizedText = AccessibilityUtils.getTextForLocale( context, locale, R.string.captioning_preview_characters); previewText.setText(localizedText); } else { previewText.setText(R.string.captioning_preview_characters); } } protected void onInstallSwitchBarToggleSwitch() { mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() { @Override public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) { mSwitchBar.setCheckedInternal(checked); Settings.Secure.putInt(getActivity().getContentResolver(), Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, checked ? 1 : 0); getPreferenceScreen().setEnabled(checked); if (mPreviewText != null) { mPreviewText.setVisibility(checked ? View.VISIBLE : View.INVISIBLE); } return false; } }); } private void installSwitchBarToggleSwitch() { onInstallSwitchBarToggleSwitch(); mSwitchBar.show(); } private void removeSwitchBarToggleSwitch() { mSwitchBar.hide(); mToggleSwitch.setOnBeforeCheckedChangeListener(null); } private void initializeAllPreferences() { mLocale = (LocalePreference) findPreference(PREF_LOCALE); mFontSize = (ListPreference) findPreference(PREF_FONT_SIZE); final Resources res = getResources(); final int[] presetValues = res.getIntArray(R.array.captioning_preset_selector_values); final String[] presetTitles = res.getStringArray(R.array.captioning_preset_selector_titles); mPreset = (PresetPreference) findPreference(PREF_PRESET); mPreset.setValues(presetValues); mPreset.setTitles(presetTitles); mCustom = (PreferenceCategory) findPreference(PREF_CUSTOM); mShowingCustom = true; final int[] colorValues = res.getIntArray(R.array.captioning_color_selector_values); final String[] colorTitles = res.getStringArray(R.array.captioning_color_selector_titles); mForegroundColor = (ColorPreference) mCustom.findPreference(PREF_FOREGROUND_COLOR); mForegroundColor.setTitles(colorTitles); mForegroundColor.setValues(colorValues); final int[] opacityValues = res.getIntArray(R.array.captioning_opacity_selector_values); final String[] opacityTitles = res.getStringArray( R.array.captioning_opacity_selector_titles); mForegroundOpacity = (ColorPreference) mCustom.findPreference(PREF_FOREGROUND_OPACITY); mForegroundOpacity.setTitles(opacityTitles); mForegroundOpacity.setValues(opacityValues); mEdgeColor = (ColorPreference) mCustom.findPreference(PREF_EDGE_COLOR); mEdgeColor.setTitles(colorTitles); mEdgeColor.setValues(colorValues); // Add "none" as an additional option for backgrounds. final int[] bgColorValues = new int[colorValues.length + 1]; final String[] bgColorTitles = new String[colorTitles.length + 1]; System.arraycopy(colorValues, 0, bgColorValues, 1, colorValues.length); System.arraycopy(colorTitles, 0, bgColorTitles, 1, colorTitles.length); bgColorValues[0] = Color.TRANSPARENT; bgColorTitles[0] = getString(R.string.color_none); mBackgroundColor = (ColorPreference) mCustom.findPreference(PREF_BACKGROUND_COLOR); mBackgroundColor.setTitles(bgColorTitles); mBackgroundColor.setValues(bgColorValues); mBackgroundOpacity = (ColorPreference) mCustom.findPreference(PREF_BACKGROUND_OPACITY); mBackgroundOpacity.setTitles(opacityTitles); mBackgroundOpacity.setValues(opacityValues); mWindowColor = (ColorPreference) mCustom.findPreference(PREF_WINDOW_COLOR); mWindowColor.setTitles(bgColorTitles); mWindowColor.setValues(bgColorValues); mWindowOpacity = (ColorPreference) mCustom.findPreference(PREF_WINDOW_OPACITY); mWindowOpacity.setTitles(opacityTitles); mWindowOpacity.setValues(opacityValues); mEdgeType = (EdgeTypePreference) mCustom.findPreference(PREF_EDGE_TYPE); mTypeface = (ListPreference) mCustom.findPreference(PREF_TYPEFACE); } private void installUpdateListeners() { mPreset.setOnValueChangedListener(this); mForegroundColor.setOnValueChangedListener(this); mForegroundOpacity.setOnValueChangedListener(this); mEdgeColor.setOnValueChangedListener(this); mBackgroundColor.setOnValueChangedListener(this); mBackgroundOpacity.setOnValueChangedListener(this); mWindowColor.setOnValueChangedListener(this); mWindowOpacity.setOnValueChangedListener(this); mEdgeType.setOnValueChangedListener(this); mTypeface.setOnPreferenceChangeListener(this); mFontSize.setOnPreferenceChangeListener(this); mLocale.setOnPreferenceChangeListener(this); } private void updateAllPreferences() { final int preset = mCaptioningManager.getRawUserStyle(); mPreset.setValue(preset); final float fontSize = mCaptioningManager.getFontScale(); mFontSize.setValue(Float.toString(fontSize)); final ContentResolver cr = getContentResolver(); final CaptionStyle attrs = CaptionStyle.getCustomStyle(cr); mEdgeType.setValue(attrs.edgeType); mEdgeColor.setValue(attrs.edgeColor); final int foregroundColor = attrs.hasForegroundColor() ? attrs.foregroundColor : CaptionStyle.COLOR_UNSPECIFIED; parseColorOpacity(mForegroundColor, mForegroundOpacity, foregroundColor); final int backgroundColor = attrs.hasBackgroundColor() ? attrs.backgroundColor : CaptionStyle.COLOR_UNSPECIFIED; parseColorOpacity(mBackgroundColor, mBackgroundOpacity, backgroundColor); final int windowColor = attrs.hasWindowColor() ? attrs.windowColor : CaptionStyle.COLOR_UNSPECIFIED; parseColorOpacity(mWindowColor, mWindowOpacity, windowColor); final String rawTypeface = attrs.mRawTypeface; mTypeface.setValue(rawTypeface == null ? "" : rawTypeface); final String rawLocale = mCaptioningManager.getRawLocale(); mLocale.setValue(rawLocale == null ? "" : rawLocale); } /** * Unpack the specified color value and update the preferences. * * @param color color preference * @param opacity opacity preference * @param value packed value */ private void parseColorOpacity(ColorPreference color, ColorPreference opacity, int value) { final int colorValue; final int opacityValue; if (!CaptionStyle.hasColor(value)) { // "Default" color with variable alpha. colorValue = CaptionStyle.COLOR_UNSPECIFIED; opacityValue = (value & 0xFF) << 24; } else if ((value >>> 24) == 0) { // "None" color with variable alpha. colorValue = Color.TRANSPARENT; opacityValue = (value & 0xFF) << 24; } else { // Normal color. colorValue = value | 0xFF000000; opacityValue = value & 0xFF000000; } // Opacity value is always white. opacity.setValue(opacityValue | 0xFFFFFF); color.setValue(colorValue); } private int mergeColorOpacity(ColorPreference color, ColorPreference opacity) { final int colorValue = color.getValue(); final int opacityValue = opacity.getValue(); final int value; // "Default" is 0x00FFFFFF or, for legacy support, 0x00000100. if (!CaptionStyle.hasColor(colorValue)) { // Encode "default" as 0x00FFFFaa. value = 0x00FFFF00 | Color.alpha(opacityValue); } else if (colorValue == Color.TRANSPARENT) { // Encode "none" as 0x000000aa. value = Color.alpha(opacityValue); } else { // Encode custom color normally. value = colorValue & 0x00FFFFFF | opacityValue & 0xFF000000; } return value; } private void refreshShowingCustom() { final boolean customPreset = mPreset.getValue() == CaptionStyle.PRESET_CUSTOM; if (!customPreset && mShowingCustom) { getPreferenceScreen().removePreference(mCustom); mShowingCustom = false; } else if (customPreset && !mShowingCustom) { getPreferenceScreen().addPreference(mCustom); mShowingCustom = true; } } @Override public void onValueChanged(ListDialogPreference preference, int value) { final ContentResolver cr = getActivity().getContentResolver(); if (mForegroundColor == preference || mForegroundOpacity == preference) { final int merged = mergeColorOpacity(mForegroundColor, mForegroundOpacity); Settings.Secure.putInt( cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, merged); } else if (mBackgroundColor == preference || mBackgroundOpacity == preference) { final int merged = mergeColorOpacity(mBackgroundColor, mBackgroundOpacity); Settings.Secure.putInt( cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, merged); } else if (mWindowColor == preference || mWindowOpacity == preference) { final int merged = mergeColorOpacity(mWindowColor, mWindowOpacity); Settings.Secure.putInt( cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, merged); } else if (mEdgeColor == preference) { Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, value); } else if (mPreset == preference) { Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET, value); refreshShowingCustom(); } else if (mEdgeType == preference) { Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, value); } refreshPreviewText(); } @Override public boolean onPreferenceChange(Preference preference, Object value) { final ContentResolver cr = getActivity().getContentResolver(); if (mTypeface == preference) { Settings.Secure.putString( cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE, (String) value); } else if (mFontSize == preference) { Settings.Secure.putFloat( cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE, Float.parseFloat((String) value)); } else if (mLocale == preference) { Settings.Secure.putString( cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE, (String) value); } refreshPreviewText(); return true; } }