/*
 * 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.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemService;
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.
/** * Contains methods for accessing and monitoring preferred video captioning state and visual * properties. */
@SystemService(Context.CAPTIONING_SERVICE) public class CaptioningManager {
Default captioning enabled value.
/** Default captioning enabled value. */
private static final int DEFAULT_ENABLED = 0;
Default style preset as an index into CaptionStyle.PRESETS.
/** Default style preset as an index into {@link CaptionStyle#PRESETS}. */
private static final int DEFAULT_PRESET = 0;
Default scaling value for caption fonts.
/** Default scaling value for caption fonts. */
private static final float DEFAULT_FONT_SCALE = 1; private final ArrayList<CaptioningChangeListener> mListeners = new ArrayList<>(); private final ContentResolver mContentResolver; private final ContentObserver mContentObserver;
Creates a new captioning manager for the specified context.
@hide
/** * Creates a new captioning manager for the specified context. * * @hide */
public CaptioningManager(Context context) { mContentResolver = context.getContentResolver(); final Handler handler = new Handler(context.getMainLooper()); mContentObserver = new MyContentObserver(handler); }
Returns:the user's preferred captioning enabled state
/** * @return the user's preferred captioning enabled state */
public final boolean isEnabled() { return Secure.getInt( mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_ENABLED, DEFAULT_ENABLED) == 1; }
Returns:the raw locale string for the user's preferred captioning language
@hide
/** * @return the raw locale string for the user's preferred captioning * language * @hide */
@Nullable public final String getRawLocale() { return Secure.getString(mContentResolver, Secure.ACCESSIBILITY_CAPTIONING_LOCALE); }
Returns:the locale for the user's preferred captioning language, or null if not specified
/** * @return the locale for the user's preferred captioning language, or null * if not specified */
@Nullable 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; }
Returns:the user's preferred font scaling factor for video captions, or 1 if not specified
/** * @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); }
Returns:the raw preset number, or the first preset if not specified
@hide
/** * @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); }
Returns:the user's preferred visual properties for captions as a CaptionStyle, or the default style if not specified
/** * @return the user's preferred visual properties for captions as a * {@link CaptionStyle}, or the default style if not specified */
@NonNull 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.
Params:
  • listener – the listener to add
/** * 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(@NonNull 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_WINDOW_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); registerObserver(Secure.ACCESSIBILITY_CAPTIONING_PRESET); } mListeners.add(listener); } } private void registerObserver(String key) { mContentResolver.registerContentObserver(Secure.getUriFor(key), false, mContentObserver); }
Removes a listener previously added using addCaptioningChangeListener.
Params:
  • listener – the listener to remove
/** * Removes a listener previously added using * {@link #addCaptioningChangeListener}. * * @param listener the listener to remove */
public void removeCaptioningChangeListener(@NonNull 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 class MyContentObserver extends ContentObserver { private final Handler mHandler; public MyContentObserver(Handler handler) { super(handler); mHandler = handler; } @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.
/** * 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.
/** * 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%.
@hide
/** * Packed value for a color of 'none' and a cached opacity of 100%. * * @hide */
private static final int COLOR_NONE_OPAQUE = 0x000000FF;
Packed value for a color of 'default' and opacity of 100%.
@hide
/** * Packed value for a color of 'default' and opacity of 100%. * * @hide */
public static final int COLOR_UNSPECIFIED = 0x00FFFFFF; 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; private static final CaptionStyle UNSPECIFIED;
The default caption style used to fill in unspecified values. @hide
/** The default caption style used to fill in unspecified values. @hide */
public static final CaptionStyle DEFAULT;
@hide
/** @hide */
public static final CaptionStyle[] PRESETS;
@hide
/** @hide */
public static final int PRESET_CUSTOM = -1;
Unspecified edge type value.
/** Unspecified edge type value. */
public static final int EDGE_TYPE_UNSPECIFIED = -1;
Edge type value specifying no character edges.
/** Edge type value specifying no character edges. */
public static final int EDGE_TYPE_NONE = 0;
Edge type value specifying uniformly outlined character edges.
/** Edge type value specifying uniformly outlined character edges. */
public static final int EDGE_TYPE_OUTLINE = 1;
Edge type value specifying drop-shadowed character edges.
/** 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.
/** Edge type value specifying raised bevel character edges. */
public static final int EDGE_TYPE_RAISED = 3;
Edge type value specifying depressed bevel character edges.
/** Edge type value specifying depressed bevel character edges. */
public static final int EDGE_TYPE_DEPRESSED = 4;
The preferred foreground color for video captions.
/** The preferred foreground color for video captions. */
public final int foregroundColor;
The preferred background color for video captions.
/** The preferred background color for video captions. */
public final int backgroundColor; /** * The preferred edge type for video captions, one of: * <ul> * <li>{@link #EDGE_TYPE_UNSPECIFIED} * <li>{@link #EDGE_TYPE_NONE} * <li>{@link #EDGE_TYPE_OUTLINE} * <li>{@link #EDGE_TYPE_DROP_SHADOW} * <li>{@link #EDGE_TYPE_RAISED} * <li>{@link #EDGE_TYPE_DEPRESSED} * </ul> */ public final int edgeType;
The preferred edge color for video captions, if using an edge type other than EDGE_TYPE_NONE.
/** * 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.
/** The preferred window color for video captions. */
public final int windowColor;
@hide
/** * @hide */
public final String mRawTypeface; private final boolean mHasForegroundColor; private final boolean mHasBackgroundColor; private final boolean mHasEdgeType; private final boolean mHasEdgeColor; private final boolean mHasWindowColor;
Lazily-created typeface based on the raw typeface string.
/** Lazily-created typeface based on the raw typeface string. */
private Typeface mParsedTypeface; private CaptionStyle(int foregroundColor, int backgroundColor, int edgeType, int edgeColor, int windowColor, String rawTypeface) { mHasForegroundColor = hasColor(foregroundColor); mHasBackgroundColor = hasColor(backgroundColor); mHasEdgeType = edgeType != EDGE_TYPE_UNSPECIFIED; mHasEdgeColor = hasColor(edgeColor); mHasWindowColor = hasColor(windowColor); // Always use valid colors, even when no override is specified, to // ensure backwards compatibility with apps targeting KitKat MR2. this.foregroundColor = mHasForegroundColor ? foregroundColor : Color.WHITE; this.backgroundColor = mHasBackgroundColor ? backgroundColor : Color.BLACK; this.edgeType = mHasEdgeType ? edgeType : EDGE_TYPE_NONE; this.edgeColor = mHasEdgeColor ? edgeColor : Color.BLACK; this.windowColor = mHasWindowColor ? windowColor : COLOR_NONE_OPAQUE; mRawTypeface = rawTypeface; }
Returns whether a packed color indicates a non-default value.
Params:
  • packedColor – the packed color value
Returns:true if a non-default value is specified
@hide
/** * Returns whether a packed color indicates a non-default value. * * @param packedColor the packed color value * @return {@code true} if a non-default value is specified * @hide */
public static boolean hasColor(int packedColor) { // Matches the color packing code from Settings. "Default" packed // colors are indicated by zero alpha and non-zero red/blue. The // cached alpha value used by Settings is stored in green. return (packedColor >>> 24) != 0 || (packedColor & 0xFFFF00) == 0; }
Applies a caption style, overriding any properties that are specified in the overlay caption.
Params:
  • overlay – The style to apply
Returns:A caption style with the overlay style applied
@hide
/** * Applies a caption style, overriding any properties that are specified * in the overlay caption. * * @param overlay The style to apply * @return A caption style with the overlay style applied * @hide */
@NonNull public CaptionStyle applyStyle(@NonNull CaptionStyle overlay) { final int newForegroundColor = overlay.hasForegroundColor() ? overlay.foregroundColor : foregroundColor; final int newBackgroundColor = overlay.hasBackgroundColor() ? overlay.backgroundColor : backgroundColor; final int newEdgeType = overlay.hasEdgeType() ? overlay.edgeType : edgeType; final int newEdgeColor = overlay.hasEdgeColor() ? overlay.edgeColor : edgeColor; final int newWindowColor = overlay.hasWindowColor() ? overlay.windowColor : windowColor; final String newRawTypeface = overlay.mRawTypeface != null ? overlay.mRawTypeface : mRawTypeface; return new CaptionStyle(newForegroundColor, newBackgroundColor, newEdgeType, newEdgeColor, newWindowColor, newRawTypeface); }
Returns:true if the user has specified a background color that should override the application default, false otherwise
/** * @return {@code true} if the user has specified a background color * that should override the application default, {@code false} * otherwise */
public boolean hasBackgroundColor() { return mHasBackgroundColor; }
Returns:true if the user has specified a foreground color that should override the application default, false otherwise
/** * @return {@code true} if the user has specified a foreground color * that should override the application default, {@code false} * otherwise */
public boolean hasForegroundColor() { return mHasForegroundColor; }
Returns:true if the user has specified an edge type that should override the application default, false otherwise
/** * @return {@code true} if the user has specified an edge type that * should override the application default, {@code false} * otherwise */
public boolean hasEdgeType() { return mHasEdgeType; }
Returns:true if the user has specified an edge color that should override the application default, false otherwise
/** * @return {@code true} if the user has specified an edge color that * should override the application default, {@code false} * otherwise */
public boolean hasEdgeColor() { return mHasEdgeColor; }
Returns:true if the user has specified a window color that should override the application default, false otherwise
/** * @return {@code true} if the user has specified a window color that * should override the application default, {@code false} * otherwise */
public boolean hasWindowColor() { return mHasWindowColor; }
Returns:the preferred Typeface for video captions, or null if not specified
/** * @return the preferred {@link Typeface} for video captions, or null if * not specified */
@Nullable public Typeface getTypeface() { if (mParsedTypeface == null && !TextUtils.isEmpty(mRawTypeface)) { mParsedTypeface = Typeface.create(mRawTypeface, Typeface.NORMAL); } return mParsedTypeface; }
@hide
/** * @hide */
@NonNull 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); UNSPECIFIED = new CaptionStyle(COLOR_UNSPECIFIED, COLOR_UNSPECIFIED, EDGE_TYPE_UNSPECIFIED, COLOR_UNSPECIFIED, COLOR_UNSPECIFIED, null); // The ordering of these cannot change since we store the index // directly in preferences. PRESETS = new CaptionStyle[] { WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE, UNSPECIFIED }; DEFAULT_CUSTOM = WHITE_ON_BLACK; DEFAULT = WHITE_ON_BLACK; } }
Listener for changes in captioning properties, including enabled state and user style preferences.
/** * 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.
Params:
  • enabled – the user's new preferred captioning enabled state
/** * 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.
Params:
  • userStyle – the user's new preferred style
See Also:
/** * Called when the captioning user style changes. * * @param userStyle the user's new preferred style * @see CaptioningManager#getUserStyle() */
public void onUserStyleChanged(@NonNull CaptionStyle userStyle) {}
Called when the captioning locale changes.
Params:
  • locale – the preferred captioning locale, or null if not specified
See Also:
/** * Called when the captioning locale changes. * * @param locale the preferred captioning locale, or {@code null} if not specified * @see CaptioningManager#getLocale() */
public void onLocaleChanged(@Nullable Locale locale) {}
Called when the captioning font scaling factor changes.
Params:
  • fontScale – the preferred font scaling factor
See Also:
/** * Called when the captioning font scaling factor changes. * * @param fontScale the preferred font scaling factor * @see CaptioningManager#getFontScale() */
public void onFontScaleChanged(float fontScale) {} } }