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