• 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 android.view.accessibility;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.database.ContentObserver;
22 import android.graphics.Color;
23 import android.graphics.Typeface;
24 import android.net.Uri;
25 import android.os.Handler;
26 import android.provider.Settings.Secure;
27 import android.text.TextUtils;
28 
29 import java.util.ArrayList;
30 import java.util.Locale;
31 
32 /**
33  * Contains methods for accessing and monitoring preferred video captioning state and visual
34  * properties.
35  * <p>
36  * To obtain a handle to the captioning manager, do the following:
37  * <p>
38  * <code>
39  * <pre>CaptioningManager captioningManager =
40  *        (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);</pre>
41  * </code>
42  */
43 public class CaptioningManager {
44     /** Default captioning enabled value. */
45     private static final int DEFAULT_ENABLED = 0;
46 
47     /** Default style preset as an index into {@link CaptionStyle#PRESETS}. */
48     private static final int DEFAULT_PRESET = 0;
49 
50     /** Default scaling value for caption fonts. */
51     private static final float DEFAULT_FONT_SCALE = 1;
52 
53     private final ArrayList<CaptioningChangeListener>
54             mListeners = new ArrayList<CaptioningChangeListener>();
55     private final Handler mHandler = new Handler();
56 
57     private final ContentResolver mContentResolver;
58 
59     /**
60      * Creates a new captioning manager for the specified context.
61      *
62      * @hide
63      */
CaptioningManager(Context context)64     public CaptioningManager(Context context) {
65         mContentResolver = context.getContentResolver();
66     }
67 
68     /**
69      * @return the user's preferred captioning enabled state
70      */
isEnabled()71     public final boolean isEnabled() {
72         return Secure.getInt(
73                 mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_ENABLED, DEFAULT_ENABLED) == 1;
74     }
75 
76     /**
77      * @return the raw locale string for the user's preferred captioning
78      *         language
79      * @hide
80      */
getRawLocale()81     public final String getRawLocale() {
82         return Secure.getString(mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
83     }
84 
85     /**
86      * @return the locale for the user's preferred captioning language, or null
87      *         if not specified
88      */
getLocale()89     public final Locale getLocale() {
90         final String rawLocale = getRawLocale();
91         if (!TextUtils.isEmpty(rawLocale)) {
92             final String[] splitLocale = rawLocale.split("_");
93             switch (splitLocale.length) {
94                 case 3:
95                     return new Locale(splitLocale[0], splitLocale[1], splitLocale[2]);
96                 case 2:
97                     return new Locale(splitLocale[0], splitLocale[1]);
98                 case 1:
99                     return new Locale(splitLocale[0]);
100             }
101         }
102 
103         return null;
104     }
105 
106     /**
107      * @return the user's preferred font scaling factor for video captions, or 1 if not
108      *         specified
109      */
getFontScale()110     public final float getFontScale() {
111         return Secure.getFloat(
112                 mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE, DEFAULT_FONT_SCALE);
113     }
114 
115     /**
116      * @return the raw preset number, or the first preset if not specified
117      * @hide
118      */
getRawUserStyle()119     public int getRawUserStyle() {
120         return Secure.getInt(
121                 mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_PRESET, DEFAULT_PRESET);
122     }
123 
124     /**
125      * @return the user's preferred visual properties for captions as a
126      *         {@link CaptionStyle}, or the default style if not specified
127      */
getUserStyle()128     public CaptionStyle getUserStyle() {
129         final int preset = getRawUserStyle();
130         if (preset == CaptionStyle.PRESET_CUSTOM) {
131             return CaptionStyle.getCustomStyle(mContentResolver);
132         }
133 
134         return CaptionStyle.PRESETS[preset];
135     }
136 
137     /**
138      * Adds a listener for changes in the user's preferred captioning enabled
139      * state and visual properties.
140      *
141      * @param listener the listener to add
142      */
addCaptioningChangeListener(CaptioningChangeListener listener)143     public void addCaptioningChangeListener(CaptioningChangeListener listener) {
144         synchronized (mListeners) {
145             if (mListeners.isEmpty()) {
146                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_ENABLED);
147                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR);
148                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR);
149                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE);
150                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR);
151                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
152                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE);
153                 registerObserver(Secure.ACCESSIBILITY_CAPTIONING_LOCALE);
154             }
155 
156             mListeners.add(listener);
157         }
158     }
159 
registerObserver(String key)160     private void registerObserver(String key) {
161         mContentResolver.registerContentObserver(Secure.getUriFor(key), false, mContentObserver);
162     }
163 
164     /**
165      * Removes a listener previously added using
166      * {@link #addCaptioningChangeListener}.
167      *
168      * @param listener the listener to remove
169      */
removeCaptioningChangeListener(CaptioningChangeListener listener)170     public void removeCaptioningChangeListener(CaptioningChangeListener listener) {
171         synchronized (mListeners) {
172             mListeners.remove(listener);
173 
174             if (mListeners.isEmpty()) {
175                 mContentResolver.unregisterContentObserver(mContentObserver);
176             }
177         }
178     }
179 
notifyEnabledChanged()180     private void notifyEnabledChanged() {
181         final boolean enabled = isEnabled();
182         synchronized (mListeners) {
183             for (CaptioningChangeListener listener : mListeners) {
184                 listener.onEnabledChanged(enabled);
185             }
186         }
187     }
188 
notifyUserStyleChanged()189     private void notifyUserStyleChanged() {
190         final CaptionStyle userStyle = getUserStyle();
191         synchronized (mListeners) {
192             for (CaptioningChangeListener listener : mListeners) {
193                 listener.onUserStyleChanged(userStyle);
194             }
195         }
196     }
197 
notifyLocaleChanged()198     private void notifyLocaleChanged() {
199         final Locale locale = getLocale();
200         synchronized (mListeners) {
201             for (CaptioningChangeListener listener : mListeners) {
202                 listener.onLocaleChanged(locale);
203             }
204         }
205     }
206 
notifyFontScaleChanged()207     private void notifyFontScaleChanged() {
208         final float fontScale = getFontScale();
209         synchronized (mListeners) {
210             for (CaptioningChangeListener listener : mListeners) {
211                 listener.onFontScaleChanged(fontScale);
212             }
213         }
214     }
215 
216     private final ContentObserver mContentObserver = new ContentObserver(mHandler) {
217         @Override
218         public void onChange(boolean selfChange, Uri uri) {
219             final String uriPath = uri.getPath();
220             final String name = uriPath.substring(uriPath.lastIndexOf('/') + 1);
221             if (Secure.ACCESSIBILITY_CAPTIONING_ENABLED.equals(name)) {
222                 notifyEnabledChanged();
223             } else if (Secure.ACCESSIBILITY_CAPTIONING_LOCALE.equals(name)) {
224                 notifyLocaleChanged();
225             } else if (Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE.equals(name)) {
226                 notifyFontScaleChanged();
227             } else {
228                 // We only need a single callback when multiple style properties
229                 // change in rapid succession.
230                 mHandler.removeCallbacks(mStyleChangedRunnable);
231                 mHandler.post(mStyleChangedRunnable);
232             }
233         }
234     };
235 
236     /**
237      * Runnable posted when user style properties change. This is used to
238      * prevent unnecessary change notifications when multiple properties change
239      * in rapid succession.
240      */
241     private final Runnable mStyleChangedRunnable = new Runnable() {
242         @Override
243         public void run() {
244             notifyUserStyleChanged();
245         }
246     };
247 
248     /**
249      * Specifies visual properties for video captions, including foreground and
250      * background colors, edge properties, and typeface.
251      */
252     public static final class CaptionStyle {
253         private static final CaptionStyle WHITE_ON_BLACK;
254         private static final CaptionStyle BLACK_ON_WHITE;
255         private static final CaptionStyle YELLOW_ON_BLACK;
256         private static final CaptionStyle YELLOW_ON_BLUE;
257         private static final CaptionStyle DEFAULT_CUSTOM;
258 
259         /** @hide */
260         public static final CaptionStyle[] PRESETS;
261 
262         /** @hide */
263         public static final int PRESET_CUSTOM = -1;
264 
265         /** Edge type value specifying no character edges. */
266         public static final int EDGE_TYPE_NONE = 0;
267 
268         /** Edge type value specifying uniformly outlined character edges. */
269         public static final int EDGE_TYPE_OUTLINE = 1;
270 
271         /** Edge type value specifying drop-shadowed character edges. */
272         public static final int EDGE_TYPE_DROP_SHADOW = 2;
273 
274         /** The preferred foreground color for video captions. */
275         public final int foregroundColor;
276 
277         /** The preferred background color for video captions. */
278         public final int backgroundColor;
279 
280         /**
281          * The preferred edge type for video captions, one of:
282          * <ul>
283          * <li>{@link #EDGE_TYPE_NONE}
284          * <li>{@link #EDGE_TYPE_OUTLINE}
285          * <li>{@link #EDGE_TYPE_DROP_SHADOW}
286          * </ul>
287          */
288         public final int edgeType;
289 
290         /**
291          * The preferred edge color for video captions, if using an edge type
292          * other than {@link #EDGE_TYPE_NONE}.
293          */
294         public final int edgeColor;
295 
296         /**
297          * @hide
298          */
299         public final String mRawTypeface;
300 
301         private Typeface mParsedTypeface;
302 
CaptionStyle(int foregroundColor, int backgroundColor, int edgeType, int edgeColor, String rawTypeface)303         private CaptionStyle(int foregroundColor, int backgroundColor, int edgeType, int edgeColor,
304                 String rawTypeface) {
305             this.foregroundColor = foregroundColor;
306             this.backgroundColor = backgroundColor;
307             this.edgeType = edgeType;
308             this.edgeColor = edgeColor;
309 
310             mRawTypeface = rawTypeface;
311         }
312 
313         /**
314          * @return the preferred {@link Typeface} for video captions, or null if
315          *         not specified
316          */
getTypeface()317         public Typeface getTypeface() {
318             if (mParsedTypeface == null && !TextUtils.isEmpty(mRawTypeface)) {
319                 mParsedTypeface = Typeface.create(mRawTypeface, Typeface.NORMAL);
320             }
321             return mParsedTypeface;
322         }
323 
324         /**
325          * @hide
326          */
getCustomStyle(ContentResolver cr)327         public static CaptionStyle getCustomStyle(ContentResolver cr) {
328             final CaptionStyle defStyle = CaptionStyle.DEFAULT_CUSTOM;
329             final int foregroundColor = Secure.getInt(
330                     cr, Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, defStyle.foregroundColor);
331             final int backgroundColor = Secure.getInt(
332                     cr, Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, defStyle.backgroundColor);
333             final int edgeType = Secure.getInt(
334                     cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, defStyle.edgeType);
335             final int edgeColor = Secure.getInt(
336                     cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, defStyle.edgeColor);
337 
338             String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
339             if (rawTypeface == null) {
340                 rawTypeface = defStyle.mRawTypeface;
341             }
342 
343             return new CaptionStyle(
344                     foregroundColor, backgroundColor, edgeType, edgeColor, rawTypeface);
345         }
346 
347         static {
348             WHITE_ON_BLACK = new CaptionStyle(
349                     Color.WHITE, Color.BLACK, EDGE_TYPE_NONE, Color.BLACK, null);
350             BLACK_ON_WHITE = new CaptionStyle(
351                     Color.BLACK, Color.WHITE, EDGE_TYPE_NONE, Color.BLACK, null);
352             YELLOW_ON_BLACK = new CaptionStyle(
353                     Color.YELLOW, Color.BLACK, EDGE_TYPE_NONE, Color.BLACK, null);
354             YELLOW_ON_BLUE = new CaptionStyle(
355                     Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE, Color.BLACK, null);
356 
357             PRESETS = new CaptionStyle[] {
358                     WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE
359             };
360 
361             DEFAULT_CUSTOM = WHITE_ON_BLACK;
362         }
363     }
364 
365     /**
366      * Listener for changes in captioning properties, including enabled state
367      * and user style preferences.
368      */
369     public static abstract class CaptioningChangeListener {
370         /**
371          * Called when the captioning enabled state changes.
372          *
373          * @param enabled the user's new preferred captioning enabled state
374          */
onEnabledChanged(boolean enabled)375         public void onEnabledChanged(boolean enabled) {
376         }
377 
378         /**
379          * Called when the captioning user style changes.
380          *
381          * @param userStyle the user's new preferred style
382          * @see CaptioningManager#getUserStyle()
383          */
onUserStyleChanged(CaptionStyle userStyle)384         public void onUserStyleChanged(CaptionStyle userStyle) {
385         }
386 
387         /**
388          * Called when the captioning locale changes.
389          *
390          * @param locale the preferred captioning locale
391          * @see CaptioningManager#getLocale()
392          */
onLocaleChanged(Locale locale)393         public void onLocaleChanged(Locale locale) {
394         }
395 
396         /**
397          * Called when the captioning font scaling factor changes.
398          *
399          * @param fontScale the preferred font scaling factor
400          * @see CaptioningManager#getFontScale()
401          */
onFontScaleChanged(float fontScale)402         public void onFontScaleChanged(float fontScale) {
403         }
404     }
405 }
406