/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.view.accessibility; import android.content.ContentResolver; import android.content.Context; import android.database.ContentObserver; import android.graphics.Color; import android.graphics.Typeface; import android.net.Uri; import android.os.Handler; import android.provider.Settings.Secure; import android.text.TextUtils; import java.util.ArrayList; import java.util.Locale; /** * Contains methods for accessing and monitoring preferred video captioning state and visual * properties. *

* To obtain a handle to the captioning manager, do the following: *

* *

CaptioningManager captioningManager =
 *        (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
* */ public class CaptioningManager { /** Default captioning enabled value. */ private static final int DEFAULT_ENABLED = 0; /** Default style preset as an index into {@link CaptionStyle#PRESETS}. */ private static final int DEFAULT_PRESET = 0; /** Default scaling value for caption fonts. */ private static final float DEFAULT_FONT_SCALE = 1; private final ArrayList mListeners = new ArrayList(); private final Handler mHandler = new Handler(); private final ContentResolver mContentResolver; /** * Creates a new captioning manager for the specified context. * * @hide */ public CaptioningManager(Context context) { mContentResolver = context.getContentResolver(); } /** * @return the user's preferred captioning enabled state */ public final boolean isEnabled() { return Secure.getInt( mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_ENABLED, DEFAULT_ENABLED) == 1; } /** * @return the raw locale string for the user's preferred captioning * language * @hide */ public final String getRawLocale() { return Secure.getString(mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_LOCALE); } /** * @return the locale for the user's preferred captioning language, or null * if not specified */ public final Locale getLocale() { final String rawLocale = getRawLocale(); if (!TextUtils.isEmpty(rawLocale)) { final String[] splitLocale = rawLocale.split("_"); switch (splitLocale.length) { case 3: return new Locale(splitLocale[0], splitLocale[1], splitLocale[2]); case 2: return new Locale(splitLocale[0], splitLocale[1]); case 1: return new Locale(splitLocale[0]); } } return null; } /** * @return the user's preferred font scaling factor for video captions, or 1 if not * specified */ public final float getFontScale() { return Secure.getFloat( mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE, DEFAULT_FONT_SCALE); } /** * @return the raw preset number, or the first preset if not specified * @hide */ public int getRawUserStyle() { return Secure.getInt( mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_PRESET, DEFAULT_PRESET); } /** * @return the user's preferred visual properties for captions as a * {@link CaptionStyle}, or the default style if not specified */ public CaptionStyle getUserStyle() { final int preset = getRawUserStyle(); if (preset == CaptionStyle.PRESET_CUSTOM) { return CaptionStyle.getCustomStyle(mContentResolver); } return CaptionStyle.PRESETS[preset]; } /** * Adds a listener for changes in the user's preferred captioning enabled * state and visual properties. * * @param listener the listener to add */ public void addCaptioningChangeListener(CaptioningChangeListener listener) { synchronized (mListeners) { if (mListeners.isEmpty()) { registerObserver(Secure.ACCESSIBILITY_CAPTIONING_ENABLED); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_LOCALE); } mListeners.add(listener); } } private void registerObserver(String key) { mContentResolver.registerContentObserver(Secure.getUriFor(key), false, mContentObserver); } /** * Removes a listener previously added using * {@link #addCaptioningChangeListener}. * * @param listener the listener to remove */ public void removeCaptioningChangeListener(CaptioningChangeListener listener) { synchronized (mListeners) { mListeners.remove(listener); if (mListeners.isEmpty()) { mContentResolver.unregisterContentObserver(mContentObserver); } } } private void notifyEnabledChanged() { final boolean enabled = isEnabled(); synchronized (mListeners) { for (CaptioningChangeListener listener : mListeners) { listener.onEnabledChanged(enabled); } } } private void notifyUserStyleChanged() { final CaptionStyle userStyle = getUserStyle(); synchronized (mListeners) { for (CaptioningChangeListener listener : mListeners) { listener.onUserStyleChanged(userStyle); } } } private void notifyLocaleChanged() { final Locale locale = getLocale(); synchronized (mListeners) { for (CaptioningChangeListener listener : mListeners) { listener.onLocaleChanged(locale); } } } private void notifyFontScaleChanged() { final float fontScale = getFontScale(); synchronized (mListeners) { for (CaptioningChangeListener listener : mListeners) { listener.onFontScaleChanged(fontScale); } } } private final ContentObserver mContentObserver = new ContentObserver(mHandler) { @Override public void onChange(boolean selfChange, Uri uri) { final String uriPath = uri.getPath(); final String name = uriPath.substring(uriPath.lastIndexOf('/') + 1); if (Secure.ACCESSIBILITY_CAPTIONING_ENABLED.equals(name)) { notifyEnabledChanged(); } else if (Secure.ACCESSIBILITY_CAPTIONING_LOCALE.equals(name)) { notifyLocaleChanged(); } else if (Secure.ACCESSIBILITY_CAPTIONING_FONT_SCALE.equals(name)) { notifyFontScaleChanged(); } else { // We only need a single callback when multiple style properties // change in rapid succession. mHandler.removeCallbacks(mStyleChangedRunnable); mHandler.post(mStyleChangedRunnable); } } }; /** * Runnable posted when user style properties change. This is used to * prevent unnecessary change notifications when multiple properties change * in rapid succession. */ private final Runnable mStyleChangedRunnable = new Runnable() { @Override public void run() { notifyUserStyleChanged(); } }; /** * Specifies visual properties for video captions, including foreground and * background colors, edge properties, and typeface. */ public static final class CaptionStyle { /** Packed value for a color of 'none' and a cached opacity of 100%. */ private static final int COLOR_NONE_OPAQUE = 0x000000FF; private static final CaptionStyle WHITE_ON_BLACK; private static final CaptionStyle BLACK_ON_WHITE; private static final CaptionStyle YELLOW_ON_BLACK; private static final CaptionStyle YELLOW_ON_BLUE; private static final CaptionStyle DEFAULT_CUSTOM; /** @hide */ public static final CaptionStyle[] PRESETS; /** @hide */ public static final int PRESET_CUSTOM = -1; /** Edge type value specifying no character edges. */ public static final int EDGE_TYPE_NONE = 0; /** Edge type value specifying uniformly outlined character edges. */ public static final int EDGE_TYPE_OUTLINE = 1; /** Edge type value specifying drop-shadowed character edges. */ public static final int EDGE_TYPE_DROP_SHADOW = 2; /** Edge type value specifying raised bevel character edges. */ public static final int EDGE_TYPE_RAISED = 3; /** Edge type value specifying depressed bevel character edges. */ public static final int EDGE_TYPE_DEPRESSED = 4; /** The preferred foreground color for video captions. */ public final int foregroundColor; /** The preferred background color for video captions. */ public final int backgroundColor; /** * The preferred edge type for video captions, one of: * */ public final int edgeType; /** * The preferred edge color for video captions, if using an edge type * other than {@link #EDGE_TYPE_NONE}. */ public final int edgeColor; /** The preferred window color for video captions. */ public final int windowColor; /** * @hide */ public final String mRawTypeface; private Typeface mParsedTypeface; private CaptionStyle(int foregroundColor, int backgroundColor, int edgeType, int edgeColor, int windowColor, String rawTypeface) { this.foregroundColor = foregroundColor; this.backgroundColor = backgroundColor; this.edgeType = edgeType; this.edgeColor = edgeColor; this.windowColor = windowColor; mRawTypeface = rawTypeface; } /** * @return the preferred {@link Typeface} for video captions, or null if * not specified */ public Typeface getTypeface() { if (mParsedTypeface == null && !TextUtils.isEmpty(mRawTypeface)) { mParsedTypeface = Typeface.create(mRawTypeface, Typeface.NORMAL); } return mParsedTypeface; } /** * @hide */ public static CaptionStyle getCustomStyle(ContentResolver cr) { final CaptionStyle defStyle = CaptionStyle.DEFAULT_CUSTOM; final int foregroundColor = Secure.getInt( cr, Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, defStyle.foregroundColor); final int backgroundColor = Secure.getInt( cr, Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, defStyle.backgroundColor); final int edgeType = Secure.getInt( cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, defStyle.edgeType); final int edgeColor = Secure.getInt( cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, defStyle.edgeColor); final int windowColor = Secure.getInt( cr, Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, defStyle.windowColor); String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE); if (rawTypeface == null) { rawTypeface = defStyle.mRawTypeface; } return new CaptionStyle(foregroundColor, backgroundColor, edgeType, edgeColor, windowColor, rawTypeface); } static { WHITE_ON_BLACK = new CaptionStyle(Color.WHITE, Color.BLACK, EDGE_TYPE_NONE, Color.BLACK, COLOR_NONE_OPAQUE, null); BLACK_ON_WHITE = new CaptionStyle(Color.BLACK, Color.WHITE, EDGE_TYPE_NONE, Color.BLACK, COLOR_NONE_OPAQUE, null); YELLOW_ON_BLACK = new CaptionStyle(Color.YELLOW, Color.BLACK, EDGE_TYPE_NONE, Color.BLACK, COLOR_NONE_OPAQUE, null); YELLOW_ON_BLUE = new CaptionStyle(Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE, Color.BLACK, COLOR_NONE_OPAQUE, null); PRESETS = new CaptionStyle[] { WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE }; DEFAULT_CUSTOM = WHITE_ON_BLACK; } } /** * Listener for changes in captioning properties, including enabled state * and user style preferences. */ public static abstract class CaptioningChangeListener { /** * Called when the captioning enabled state changes. * * @param enabled the user's new preferred captioning enabled state */ public void onEnabledChanged(boolean enabled) { } /** * Called when the captioning user style changes. * * @param userStyle the user's new preferred style * @see CaptioningManager#getUserStyle() */ public void onUserStyleChanged(CaptionStyle userStyle) { } /** * Called when the captioning locale changes. * * @param locale the preferred captioning locale * @see CaptioningManager#getLocale() */ public void onLocaleChanged(Locale locale) { } /** * Called when the captioning font scaling factor changes. * * @param fontScale the preferred font scaling factor * @see CaptioningManager#getFontScale() */ public void onFontScaleChanged(float fontScale) { } } }