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.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