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