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