blob: a0134d6ebc36e144feb485687b3dc6e0c6961e48 [file] [log] [blame]
/*
* 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.
* <p>
* To obtain a handle to the captioning manager, do the following:
* <p>
* <code>
* <pre>CaptioningManager captioningManager =
* (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);</pre>
* </code>
*/
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<CaptioningChangeListener>
mListeners = new ArrayList<CaptioningChangeListener>();
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:
* <ul>
* <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 {@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) {
}
}
}