Home | History | Annotate | Download | only in accessibility
      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.setCheckedInternal(enabled);
    162         mToggleSwitch = mSwitchBar.getSwitch();
    163 
    164         getPreferenceScreen().setEnabled(enabled);
    165 
    166         refreshPreviewText();
    167 
    168         installSwitchBarToggleSwitch();
    169     }
    170 
    171     @Override
    172     public void onDestroyView() {
    173         super.onDestroyView();
    174         removeSwitchBarToggleSwitch();
    175     }
    176 
    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 
    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 
    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 
    252     private void installSwitchBarToggleSwitch() {
    253         onInstallSwitchBarToggleSwitch();
    254         mSwitchBar.show();
    255     }
    256 
    257     private void removeSwitchBarToggleSwitch() {
    258         mSwitchBar.hide();
    259         mToggleSwitch.setOnBeforeCheckedChangeListener(null);
    260     }
    261 
    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 
    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 
    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      */
    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 
    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 
    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
    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
    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