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