CaptioningManager.java revision d43daf361e993457e64eeeddab6d1a0ebc828c99
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
17package android.view.accessibility;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.database.ContentObserver;
22import android.graphics.Color;
23import android.graphics.Typeface;
24import android.net.Uri;
25import android.os.Handler;
26import android.provider.Settings.Secure;
27import android.text.TextUtils;
28
29import java.util.ArrayList;
30import 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 */
43public 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     */
64    public CaptioningManager(Context context) {
65        mContentResolver = context.getContentResolver();
66    }
67
68    /**
69     * @return the user's preferred captioning enabled state
70     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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
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
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
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
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
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         */
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         */
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         */
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         */
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         */
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         */
402        public void onFontScaleChanged(float fontScale) {
403        }
404    }
405}
406