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