• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.settings.SettingsEnums;
20 import android.content.ContentResolver;
21 import android.content.Context;
22 import android.content.res.Resources;
23 import android.graphics.Color;
24 import android.os.Bundle;
25 import android.provider.Settings;
26 import android.view.View;
27 import android.view.accessibility.CaptioningManager;
28 import android.view.accessibility.CaptioningManager.CaptionStyle;
29 
30 import androidx.preference.ListPreference;
31 import androidx.preference.Preference;
32 import androidx.preference.Preference.OnPreferenceChangeListener;
33 import androidx.preference.PreferenceCategory;
34 
35 import com.android.internal.widget.SubtitleView;
36 import com.android.settings.R;
37 import com.android.settings.SettingsActivity;
38 import com.android.settings.SettingsPreferenceFragment;
39 import com.android.settings.accessibility.ListDialogPreference.OnValueChangedListener;
40 import com.android.settings.widget.SwitchBar;
41 import com.android.settings.widget.ToggleSwitch;
42 import com.android.settings.widget.ToggleSwitch.OnBeforeCheckedChangeListener;
43 import com.android.settingslib.accessibility.AccessibilityUtils;
44 import com.android.settingslib.widget.LayoutPreference;
45 
46 import java.util.Locale;
47 
48 /**
49  * Settings fragment containing captioning properties.
50  */
51 public class CaptionPropertiesFragment extends SettingsPreferenceFragment
52         implements OnPreferenceChangeListener, OnValueChangedListener {
53     private static final String PREF_CAPTION_PREVIEW = "caption_preview";
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
getMetricsCategory()98     public int getMetricsCategory() {
99         return SettingsEnums.ACCESSIBILITY_CAPTION_PROPERTIES;
100     }
101 
102     @Override
onCreate(Bundle icicle)103     public void onCreate(Bundle icicle) {
104         super.onCreate(icicle);
105 
106         mCaptioningManager = (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE);
107 
108         addPreferencesFromResource(R.xml.captioning_settings);
109         initializeAllPreferences();
110         updateAllPreferences();
111         refreshShowingCustom();
112         installUpdateListeners();
113     }
114 
115     @Override
onActivityCreated(Bundle savedInstanceState)116     public void onActivityCreated(Bundle savedInstanceState) {
117         super.onActivityCreated(savedInstanceState);
118 
119         final boolean enabled = mCaptioningManager.isEnabled();
120         SettingsActivity activity = (SettingsActivity) getActivity();
121         mSwitchBar = activity.getSwitchBar();
122         mSwitchBar.setSwitchBarText(R.string.accessibility_caption_master_switch_title,
123                 R.string.accessibility_caption_master_switch_title);
124         mSwitchBar.setCheckedInternal(enabled);
125         mToggleSwitch = mSwitchBar.getSwitch();
126 
127         getPreferenceScreen().setEnabled(enabled);
128 
129         refreshPreviewText();
130 
131         installSwitchBarToggleSwitch();
132     }
133 
134     @Override
onDestroyView()135     public void onDestroyView() {
136         super.onDestroyView();
137         removeSwitchBarToggleSwitch();
138     }
139 
refreshPreviewText()140     private void refreshPreviewText() {
141         final Context context = getActivity();
142         if (context == null) {
143             // We've been destroyed, abort!
144             return;
145         }
146 
147         final SubtitleView preview = mPreviewText;
148         if (preview != null) {
149             final int styleId = mCaptioningManager.getRawUserStyle();
150             applyCaptionProperties(mCaptioningManager, preview, mPreviewViewport, styleId);
151 
152             final Locale locale = mCaptioningManager.getLocale();
153             if (locale != null) {
154                 final CharSequence localizedText = AccessibilityUtils.getTextForLocale(
155                         context, locale, R.string.captioning_preview_text);
156                 preview.setText(localizedText);
157             } else {
158                 preview.setText(R.string.captioning_preview_text);
159             }
160 
161             final CaptionStyle style = mCaptioningManager.getUserStyle();
162             if (style.hasWindowColor()) {
163                 mPreviewWindow.setBackgroundColor(style.windowColor);
164             } else {
165                 final CaptionStyle defStyle = CaptionStyle.DEFAULT;
166                 mPreviewWindow.setBackgroundColor(defStyle.windowColor);
167             }
168         }
169     }
170 
applyCaptionProperties(CaptioningManager manager, SubtitleView previewText, View previewWindow, int styleId)171     public static void applyCaptionProperties(CaptioningManager manager, SubtitleView previewText,
172             View previewWindow, int styleId) {
173         previewText.setStyle(styleId);
174 
175         final Context context = previewText.getContext();
176         final ContentResolver cr = context.getContentResolver();
177         final float fontScale = manager.getFontScale();
178         if (previewWindow != null) {
179             // Assume the viewport is clipped with a 16:9 aspect ratio.
180             final float virtualHeight = Math.max(9 * previewWindow.getWidth(),
181                     16 * previewWindow.getHeight()) / 16.0f;
182             previewText.setTextSize(virtualHeight * LINE_HEIGHT_RATIO * fontScale);
183         } else {
184             final float textSize = context.getResources().getDimension(
185                     R.dimen.caption_preview_text_size);
186             previewText.setTextSize(textSize * fontScale);
187         }
188 
189         final Locale locale = manager.getLocale();
190         if (locale != null) {
191             final CharSequence localizedText = AccessibilityUtils.getTextForLocale(
192                     context, locale, R.string.captioning_preview_characters);
193             previewText.setText(localizedText);
194         } else {
195             previewText.setText(R.string.captioning_preview_characters);
196         }
197     }
198 
onInstallSwitchBarToggleSwitch()199     protected void onInstallSwitchBarToggleSwitch() {
200         mToggleSwitch.setOnBeforeCheckedChangeListener(new OnBeforeCheckedChangeListener() {
201             @Override
202             public boolean onBeforeCheckedChanged(ToggleSwitch toggleSwitch, boolean checked) {
203                 mSwitchBar.setCheckedInternal(checked);
204                 Settings.Secure.putInt(getActivity().getContentResolver(),
205                         Settings.Secure.ACCESSIBILITY_CAPTIONING_ENABLED, checked ? 1 : 0);
206                 getPreferenceScreen().setEnabled(checked);
207                 if (mPreviewText != null) {
208                     mPreviewText.setVisibility(checked ? View.VISIBLE : View.INVISIBLE);
209                 }
210                 return false;
211             }
212         });
213     }
214 
installSwitchBarToggleSwitch()215     private void installSwitchBarToggleSwitch() {
216         onInstallSwitchBarToggleSwitch();
217         mSwitchBar.show();
218     }
219 
removeSwitchBarToggleSwitch()220     private void removeSwitchBarToggleSwitch() {
221         mSwitchBar.hide();
222         mToggleSwitch.setOnBeforeCheckedChangeListener(null);
223     }
224 
initializeAllPreferences()225     private void initializeAllPreferences() {
226         final LayoutPreference captionPreview = findPreference(PREF_CAPTION_PREVIEW);
227 
228         final boolean enabled = mCaptioningManager.isEnabled();
229         mPreviewText = captionPreview.findViewById(R.id.preview_text);
230         mPreviewText.setVisibility(enabled ? View.VISIBLE : View.INVISIBLE);
231 
232         mPreviewWindow = captionPreview.findViewById(R.id.preview_window);
233 
234         mPreviewViewport = captionPreview.findViewById(R.id.preview_viewport);
235         mPreviewViewport.addOnLayoutChangeListener(
236                 (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom)
237                         -> refreshPreviewText());
238 
239         mLocale = (LocalePreference) findPreference(PREF_LOCALE);
240         mFontSize = (ListPreference) findPreference(PREF_FONT_SIZE);
241 
242         final Resources res = getResources();
243         final int[] presetValues = res.getIntArray(R.array.captioning_preset_selector_values);
244         final String[] presetTitles = res.getStringArray(R.array.captioning_preset_selector_titles);
245         mPreset = (PresetPreference) findPreference(PREF_PRESET);
246         mPreset.setValues(presetValues);
247         mPreset.setTitles(presetTitles);
248 
249         mCustom = (PreferenceCategory) findPreference(PREF_CUSTOM);
250         mShowingCustom = true;
251 
252         final int[] colorValues = res.getIntArray(R.array.captioning_color_selector_values);
253         final String[] colorTitles = res.getStringArray(R.array.captioning_color_selector_titles);
254         mForegroundColor = (ColorPreference) mCustom.findPreference(PREF_FOREGROUND_COLOR);
255         mForegroundColor.setTitles(colorTitles);
256         mForegroundColor.setValues(colorValues);
257 
258         final int[] opacityValues = res.getIntArray(R.array.captioning_opacity_selector_values);
259         final String[] opacityTitles = res.getStringArray(
260                 R.array.captioning_opacity_selector_titles);
261         mForegroundOpacity = (ColorPreference) mCustom.findPreference(PREF_FOREGROUND_OPACITY);
262         mForegroundOpacity.setTitles(opacityTitles);
263         mForegroundOpacity.setValues(opacityValues);
264 
265         mEdgeColor = (ColorPreference) mCustom.findPreference(PREF_EDGE_COLOR);
266         mEdgeColor.setTitles(colorTitles);
267         mEdgeColor.setValues(colorValues);
268 
269         // Add "none" as an additional option for backgrounds.
270         final int[] bgColorValues = new int[colorValues.length + 1];
271         final String[] bgColorTitles = new String[colorTitles.length + 1];
272         System.arraycopy(colorValues, 0, bgColorValues, 1, colorValues.length);
273         System.arraycopy(colorTitles, 0, bgColorTitles, 1, colorTitles.length);
274         bgColorValues[0] = Color.TRANSPARENT;
275         bgColorTitles[0] = getString(R.string.color_none);
276         mBackgroundColor = (ColorPreference) mCustom.findPreference(PREF_BACKGROUND_COLOR);
277         mBackgroundColor.setTitles(bgColorTitles);
278         mBackgroundColor.setValues(bgColorValues);
279 
280         mBackgroundOpacity = (ColorPreference) mCustom.findPreference(PREF_BACKGROUND_OPACITY);
281         mBackgroundOpacity.setTitles(opacityTitles);
282         mBackgroundOpacity.setValues(opacityValues);
283 
284         mWindowColor = (ColorPreference) mCustom.findPreference(PREF_WINDOW_COLOR);
285         mWindowColor.setTitles(bgColorTitles);
286         mWindowColor.setValues(bgColorValues);
287 
288         mWindowOpacity = (ColorPreference) mCustom.findPreference(PREF_WINDOW_OPACITY);
289         mWindowOpacity.setTitles(opacityTitles);
290         mWindowOpacity.setValues(opacityValues);
291 
292         mEdgeType = (EdgeTypePreference) mCustom.findPreference(PREF_EDGE_TYPE);
293         mTypeface = (ListPreference) mCustom.findPreference(PREF_TYPEFACE);
294     }
295 
installUpdateListeners()296     private void installUpdateListeners() {
297         mPreset.setOnValueChangedListener(this);
298         mForegroundColor.setOnValueChangedListener(this);
299         mForegroundOpacity.setOnValueChangedListener(this);
300         mEdgeColor.setOnValueChangedListener(this);
301         mBackgroundColor.setOnValueChangedListener(this);
302         mBackgroundOpacity.setOnValueChangedListener(this);
303         mWindowColor.setOnValueChangedListener(this);
304         mWindowOpacity.setOnValueChangedListener(this);
305         mEdgeType.setOnValueChangedListener(this);
306 
307         mTypeface.setOnPreferenceChangeListener(this);
308         mFontSize.setOnPreferenceChangeListener(this);
309         mLocale.setOnPreferenceChangeListener(this);
310     }
311 
updateAllPreferences()312     private void updateAllPreferences() {
313         final int preset = mCaptioningManager.getRawUserStyle();
314         mPreset.setValue(preset);
315 
316         final float fontSize = mCaptioningManager.getFontScale();
317         mFontSize.setValue(Float.toString(fontSize));
318 
319         final ContentResolver cr = getContentResolver();
320         final CaptionStyle attrs = CaptionStyle.getCustomStyle(cr);
321         mEdgeType.setValue(attrs.edgeType);
322         mEdgeColor.setValue(attrs.edgeColor);
323 
324         final int foregroundColor = attrs.hasForegroundColor() ?
325                 attrs.foregroundColor : CaptionStyle.COLOR_UNSPECIFIED;
326         parseColorOpacity(mForegroundColor, mForegroundOpacity, foregroundColor);
327 
328         final int backgroundColor = attrs.hasBackgroundColor() ?
329                 attrs.backgroundColor : CaptionStyle.COLOR_UNSPECIFIED;
330         parseColorOpacity(mBackgroundColor, mBackgroundOpacity, backgroundColor);
331 
332         final int windowColor = attrs.hasWindowColor() ?
333                 attrs.windowColor : CaptionStyle.COLOR_UNSPECIFIED;
334         parseColorOpacity(mWindowColor, mWindowOpacity, windowColor);
335 
336         final String rawTypeface = attrs.mRawTypeface;
337         mTypeface.setValue(rawTypeface == null ? "" : rawTypeface);
338 
339         final String rawLocale = mCaptioningManager.getRawLocale();
340         mLocale.setValue(rawLocale == null ? "" : rawLocale);
341     }
342 
343     /**
344      * Unpack the specified color value and update the preferences.
345      *
346      * @param color   color preference
347      * @param opacity opacity preference
348      * @param value   packed value
349      */
parseColorOpacity(ColorPreference color, ColorPreference opacity, int value)350     private void parseColorOpacity(ColorPreference color, ColorPreference opacity, int value) {
351         final int colorValue;
352         final int opacityValue;
353         if (!CaptionStyle.hasColor(value)) {
354             // "Default" color with variable alpha.
355             colorValue = CaptionStyle.COLOR_UNSPECIFIED;
356             opacityValue = (value & 0xFF) << 24;
357         } else if ((value >>> 24) == 0) {
358             // "None" color with variable alpha.
359             colorValue = Color.TRANSPARENT;
360             opacityValue = (value & 0xFF) << 24;
361         } else {
362             // Normal color.
363             colorValue = value | 0xFF000000;
364             opacityValue = value & 0xFF000000;
365         }
366 
367         // Opacity value is always white.
368         opacity.setValue(opacityValue | 0xFFFFFF);
369         color.setValue(colorValue);
370     }
371 
mergeColorOpacity(ColorPreference color, ColorPreference opacity)372     private int mergeColorOpacity(ColorPreference color, ColorPreference opacity) {
373         final int colorValue = color.getValue();
374         final int opacityValue = opacity.getValue();
375         final int value;
376         // "Default" is 0x00FFFFFF or, for legacy support, 0x00000100.
377         if (!CaptionStyle.hasColor(colorValue)) {
378             // Encode "default" as 0x00FFFFaa.
379             value = 0x00FFFF00 | Color.alpha(opacityValue);
380         } else if (colorValue == Color.TRANSPARENT) {
381             // Encode "none" as 0x000000aa.
382             value = Color.alpha(opacityValue);
383         } else {
384             // Encode custom color normally.
385             value = colorValue & 0x00FFFFFF | opacityValue & 0xFF000000;
386         }
387         return value;
388     }
389 
refreshShowingCustom()390     private void refreshShowingCustom() {
391         final boolean customPreset = mPreset.getValue() == CaptionStyle.PRESET_CUSTOM;
392         if (!customPreset && mShowingCustom) {
393             getPreferenceScreen().removePreference(mCustom);
394             mShowingCustom = false;
395         } else if (customPreset && !mShowingCustom) {
396             getPreferenceScreen().addPreference(mCustom);
397             mShowingCustom = true;
398         }
399     }
400 
401     @Override
onValueChanged(ListDialogPreference preference, int value)402     public void onValueChanged(ListDialogPreference preference, int value) {
403         final ContentResolver cr = getActivity().getContentResolver();
404         if (mForegroundColor == preference || mForegroundOpacity == preference) {
405             final int merged = mergeColorOpacity(mForegroundColor, mForegroundOpacity);
406             Settings.Secure.putInt(
407                     cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, merged);
408         } else if (mBackgroundColor == preference || mBackgroundOpacity == preference) {
409             final int merged = mergeColorOpacity(mBackgroundColor, mBackgroundOpacity);
410             Settings.Secure.putInt(
411                     cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, merged);
412         } else if (mWindowColor == preference || mWindowOpacity == preference) {
413             final int merged = mergeColorOpacity(mWindowColor, mWindowOpacity);
414             Settings.Secure.putInt(
415                     cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, merged);
416         } else if (mEdgeColor == preference) {
417             Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, value);
418         } else if (mPreset == preference) {
419             Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_PRESET, value);
420             refreshShowingCustom();
421         } else if (mEdgeType == preference) {
422             Settings.Secure.putInt(cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, value);
423         }
424 
425         refreshPreviewText();
426     }
427 
428     @Override
onPreferenceChange(Preference preference, Object value)429     public boolean onPreferenceChange(Preference preference, Object value) {
430         final ContentResolver cr = getActivity().getContentResolver();
431         if (mTypeface == preference) {
432             Settings.Secure.putString(
433                     cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE, (String) value);
434         } else if (mFontSize == preference) {
435             Settings.Secure.putFloat(
436                     cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE,
437                     Float.parseFloat((String) value));
438         } else if (mLocale == preference) {
439             Settings.Secure.putString(
440                     cr, Settings.Secure.ACCESSIBILITY_CAPTIONING_LOCALE, (String) value);
441         }
442 
443         refreshPreviewText();
444         return true;
445     }
446 }
447