CaptioningManager.java revision ce32ea7345f0157a595b1dd4306a9a65f444d7c2
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        /** Packed value for a color of 'none' and a cached opacity of 100%. */
254        private static final int COLOR_NONE_OPAQUE = 0x000000FF;
255
256        private static final CaptionStyle WHITE_ON_BLACK;
257        private static final CaptionStyle BLACK_ON_WHITE;
258        private static final CaptionStyle YELLOW_ON_BLACK;
259        private static final CaptionStyle YELLOW_ON_BLUE;
260        private static final CaptionStyle DEFAULT_CUSTOM;
261
262        /** @hide */
263        public static final CaptionStyle[] PRESETS;
264
265        /** @hide */
266        public static final int PRESET_CUSTOM = -1;
267
268        /** Edge type value specifying no character edges. */
269        public static final int EDGE_TYPE_NONE = 0;
270
271        /** Edge type value specifying uniformly outlined character edges. */
272        public static final int EDGE_TYPE_OUTLINE = 1;
273
274        /** Edge type value specifying drop-shadowed character edges. */
275        public static final int EDGE_TYPE_DROP_SHADOW = 2;
276
277        /** Edge type value specifying raised bevel character edges. */
278        public static final int EDGE_TYPE_RAISED = 3;
279
280        /** Edge type value specifying depressed bevel character edges. */
281        public static final int EDGE_TYPE_DEPRESSED = 4;
282
283        /** The preferred foreground color for video captions. */
284        public final int foregroundColor;
285
286        /** The preferred background color for video captions. */
287        public final int backgroundColor;
288
289        /**
290         * The preferred edge type for video captions, one of:
291         * <ul>
292         * <li>{@link #EDGE_TYPE_NONE}
293         * <li>{@link #EDGE_TYPE_OUTLINE}
294         * <li>{@link #EDGE_TYPE_DROP_SHADOW}
295         * <li>{@link #EDGE_TYPE_RAISED}
296         * <li>{@link #EDGE_TYPE_DEPRESSED}
297         * </ul>
298         */
299        public final int edgeType;
300
301        /**
302         * The preferred edge color for video captions, if using an edge type
303         * other than {@link #EDGE_TYPE_NONE}.
304         */
305        public final int edgeColor;
306
307        /** The preferred window color for video captions. */
308        public final int windowColor;
309
310        /**
311         * @hide
312         */
313        public final String mRawTypeface;
314
315        private Typeface mParsedTypeface;
316
317        private CaptionStyle(int foregroundColor, int backgroundColor, int edgeType, int edgeColor,
318                int windowColor, String rawTypeface) {
319            this.foregroundColor = foregroundColor;
320            this.backgroundColor = backgroundColor;
321            this.edgeType = edgeType;
322            this.edgeColor = edgeColor;
323            this.windowColor = windowColor;
324
325            mRawTypeface = rawTypeface;
326        }
327
328        /**
329         * @return the preferred {@link Typeface} for video captions, or null if
330         *         not specified
331         */
332        public Typeface getTypeface() {
333            if (mParsedTypeface == null && !TextUtils.isEmpty(mRawTypeface)) {
334                mParsedTypeface = Typeface.create(mRawTypeface, Typeface.NORMAL);
335            }
336            return mParsedTypeface;
337        }
338
339        /**
340         * @hide
341         */
342        public static CaptionStyle getCustomStyle(ContentResolver cr) {
343            final CaptionStyle defStyle = CaptionStyle.DEFAULT_CUSTOM;
344            final int foregroundColor = Secure.getInt(
345                    cr, Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, defStyle.foregroundColor);
346            final int backgroundColor = Secure.getInt(
347                    cr, Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, defStyle.backgroundColor);
348            final int edgeType = Secure.getInt(
349                    cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, defStyle.edgeType);
350            final int edgeColor = Secure.getInt(
351                    cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, defStyle.edgeColor);
352            final int windowColor = Secure.getInt(
353                    cr, Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, defStyle.windowColor);
354
355            String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE);
356            if (rawTypeface == null) {
357                rawTypeface = defStyle.mRawTypeface;
358            }
359
360            return new CaptionStyle(foregroundColor, backgroundColor, edgeType, edgeColor,
361                    windowColor, rawTypeface);
362        }
363
364        static {
365            WHITE_ON_BLACK = new CaptionStyle(Color.WHITE, Color.BLACK, EDGE_TYPE_NONE,
366                    Color.BLACK, COLOR_NONE_OPAQUE, null);
367            BLACK_ON_WHITE = new CaptionStyle(Color.BLACK, Color.WHITE, EDGE_TYPE_NONE,
368                    Color.BLACK, COLOR_NONE_OPAQUE, null);
369            YELLOW_ON_BLACK = new CaptionStyle(Color.YELLOW, Color.BLACK, EDGE_TYPE_NONE,
370                    Color.BLACK, COLOR_NONE_OPAQUE, null);
371            YELLOW_ON_BLUE = new CaptionStyle(Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE,
372                    Color.BLACK, COLOR_NONE_OPAQUE, null);
373
374            PRESETS = new CaptionStyle[] {
375                    WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE
376            };
377
378            DEFAULT_CUSTOM = WHITE_ON_BLACK;
379        }
380    }
381
382    /**
383     * Listener for changes in captioning properties, including enabled state
384     * and user style preferences.
385     */
386    public static abstract class CaptioningChangeListener {
387        /**
388         * Called when the captioning enabled state changes.
389         *
390         * @param enabled the user's new preferred captioning enabled state
391         */
392        public void onEnabledChanged(boolean enabled) {
393        }
394
395        /**
396         * Called when the captioning user style changes.
397         *
398         * @param userStyle the user's new preferred style
399         * @see CaptioningManager#getUserStyle()
400         */
401        public void onUserStyleChanged(CaptionStyle userStyle) {
402        }
403
404        /**
405         * Called when the captioning locale changes.
406         *
407         * @param locale the preferred captioning locale
408         * @see CaptioningManager#getLocale()
409         */
410        public void onLocaleChanged(Locale locale) {
411        }
412
413        /**
414         * Called when the captioning font scaling factor changes.
415         *
416         * @param fontScale the preferred font scaling factor
417         * @see CaptioningManager#getFontScale()
418         */
419        public void onFontScaleChanged(float fontScale) {
420        }
421    }
422}
423