CaptioningManager.java revision 55d70620d9fda8afafb2fdec59757a710eec0e89
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        /** The preferred window color for video captions. */
297        public final int windowColor;
298
299        /**
300         * @hide
301         */
302        public final String mRawTypeface;
303
304        private Typeface mParsedTypeface;
305
306        private CaptionStyle(int foregroundColor, int backgroundColor, int edgeType, int edgeColor,
307                int windowColor, String rawTypeface) {
308            this.foregroundColor = foregroundColor;
309            this.backgroundColor = backgroundColor;
310            this.edgeType = edgeType;
311            this.edgeColor = edgeColor;
312            this.windowColor = windowColor;
313
314            mRawTypeface = rawTypeface;
315        }
316
317        /**
318         * @return the preferred {@link Typeface} for video captions, or null if
319         *         not specified
320         */
321        public Typeface getTypeface() {
322            if (mParsedTypeface == null && !TextUtils.isEmpty(mRawTypeface)) {
323                mParsedTypeface = Typeface.create(mRawTypeface, Typeface.NORMAL);
324            }
325            return mParsedTypeface;
326        }
327
328        /**
329         * @hide
330         */
331        public static CaptionStyle getCustomStyle(ContentResolver cr) {
332            final CaptionStyle defStyle = CaptionStyle.DEFAULT_CUSTOM;
333            final int foregroundColor = Secure.getInt(
334                    cr, Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, defStyle.foregroundColor);
335            final int backgroundColor = Secure.getInt(
336                    cr, Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, defStyle.backgroundColor);
337            final int edgeType = Secure.getInt(
338                    cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, defStyle.edgeType);
339            final int edgeColor = Secure.getInt(
340                    cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, defStyle.edgeColor);
341            final int windowColor = Secure.getInt(
342                    cr, Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, defStyle.windowColor);
343
344            String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
345            if (rawTypeface == null) {
346                rawTypeface = defStyle.mRawTypeface;
347            }
348
349            return new CaptionStyle(foregroundColor, backgroundColor, edgeType, edgeColor,
350                    windowColor, rawTypeface);
351        }
352
353        static {
354            WHITE_ON_BLACK = new CaptionStyle(Color.WHITE, Color.BLACK, EDGE_TYPE_NONE,
355                    Color.BLACK, Color.TRANSPARENT, null);
356            BLACK_ON_WHITE = new CaptionStyle(Color.BLACK, Color.WHITE, EDGE_TYPE_NONE,
357                    Color.BLACK, Color.TRANSPARENT, null);
358            YELLOW_ON_BLACK = new CaptionStyle(Color.YELLOW, Color.BLACK, EDGE_TYPE_NONE,
359                    Color.BLACK, Color.TRANSPARENT, null);
360            YELLOW_ON_BLUE = new CaptionStyle(Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE,
361                    Color.BLACK, Color.TRANSPARENT, null);
362
363            PRESETS = new CaptionStyle[] {
364                    WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE
365            };
366
367            DEFAULT_CUSTOM = WHITE_ON_BLACK;
368        }
369    }
370
371    /**
372     * Listener for changes in captioning properties, including enabled state
373     * and user style preferences.
374     */
375    public static abstract class CaptioningChangeListener {
376        /**
377         * Called when the captioning enabled state changes.
378         *
379         * @param enabled the user's new preferred captioning enabled state
380         */
381        public void onEnabledChanged(boolean enabled) {
382        }
383
384        /**
385         * Called when the captioning user style changes.
386         *
387         * @param userStyle the user's new preferred style
388         * @see CaptioningManager#getUserStyle()
389         */
390        public void onUserStyleChanged(CaptionStyle userStyle) {
391        }
392
393        /**
394         * Called when the captioning locale changes.
395         *
396         * @param locale the preferred captioning locale
397         * @see CaptioningManager#getLocale()
398         */
399        public void onLocaleChanged(Locale locale) {
400        }
401
402        /**
403         * Called when the captioning font scaling factor changes.
404         *
405         * @param fontScale the preferred font scaling factor
406         * @see CaptioningManager#getFontScale()
407         */
408        public void onFontScaleChanged(float fontScale) {
409        }
410    }
411}
412