CaptioningManager.java revision ce32ea7345f0157a595b1dd4306a9a65f444d7c2
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 /** Packed value for a color of 'none' and a cached opacity of 100%. */ 254 private static final int COLOR_NONE_OPAQUE = 0x000000FF; 255 256 private static final CaptionStyle WHITE_ON_BLACK; 257 private static final CaptionStyle BLACK_ON_WHITE; 258 private static final CaptionStyle YELLOW_ON_BLACK; 259 private static final CaptionStyle YELLOW_ON_BLUE; 260 private static final CaptionStyle DEFAULT_CUSTOM; 261 262 /** @hide */ 263 public static final CaptionStyle[] PRESETS; 264 265 /** @hide */ 266 public static final int PRESET_CUSTOM = -1; 267 268 /** Edge type value specifying no character edges. */ 269 public static final int EDGE_TYPE_NONE = 0; 270 271 /** Edge type value specifying uniformly outlined character edges. */ 272 public static final int EDGE_TYPE_OUTLINE = 1; 273 274 /** Edge type value specifying drop-shadowed character edges. */ 275 public static final int EDGE_TYPE_DROP_SHADOW = 2; 276 277 /** Edge type value specifying raised bevel character edges. */ 278 public static final int EDGE_TYPE_RAISED = 3; 279 280 /** Edge type value specifying depressed bevel character edges. */ 281 public static final int EDGE_TYPE_DEPRESSED = 4; 282 283 /** The preferred foreground color for video captions. */ 284 public final int foregroundColor; 285 286 /** The preferred background color for video captions. */ 287 public final int backgroundColor; 288 289 /** 290 * The preferred edge type for video captions, one of: 291 * <ul> 292 * <li>{@link #EDGE_TYPE_NONE} 293 * <li>{@link #EDGE_TYPE_OUTLINE} 294 * <li>{@link #EDGE_TYPE_DROP_SHADOW} 295 * <li>{@link #EDGE_TYPE_RAISED} 296 * <li>{@link #EDGE_TYPE_DEPRESSED} 297 * </ul> 298 */ 299 public final int edgeType; 300 301 /** 302 * The preferred edge color for video captions, if using an edge type 303 * other than {@link #EDGE_TYPE_NONE}. 304 */ 305 public final int edgeColor; 306 307 /** The preferred window color for video captions. */ 308 public final int windowColor; 309 310 /** 311 * @hide 312 */ 313 public final String mRawTypeface; 314 315 private Typeface mParsedTypeface; 316 317 private CaptionStyle(int foregroundColor, int backgroundColor, int edgeType, int edgeColor, 318 int windowColor, String rawTypeface) { 319 this.foregroundColor = foregroundColor; 320 this.backgroundColor = backgroundColor; 321 this.edgeType = edgeType; 322 this.edgeColor = edgeColor; 323 this.windowColor = windowColor; 324 325 mRawTypeface = rawTypeface; 326 } 327 328 /** 329 * @return the preferred {@link Typeface} for video captions, or null if 330 * not specified 331 */ 332 public Typeface getTypeface() { 333 if (mParsedTypeface == null && !TextUtils.isEmpty(mRawTypeface)) { 334 mParsedTypeface = Typeface.create(mRawTypeface, Typeface.NORMAL); 335 } 336 return mParsedTypeface; 337 } 338 339 /** 340 * @hide 341 */ 342 public static CaptionStyle getCustomStyle(ContentResolver cr) { 343 final CaptionStyle defStyle = CaptionStyle.DEFAULT_CUSTOM; 344 final int foregroundColor = Secure.getInt( 345 cr, Secure.ACCESSIBILITY_CAPTIONING_FOREGROUND_COLOR, defStyle.foregroundColor); 346 final int backgroundColor = Secure.getInt( 347 cr, Secure.ACCESSIBILITY_CAPTIONING_BACKGROUND_COLOR, defStyle.backgroundColor); 348 final int edgeType = Secure.getInt( 349 cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_TYPE, defStyle.edgeType); 350 final int edgeColor = Secure.getInt( 351 cr, Secure.ACCESSIBILITY_CAPTIONING_EDGE_COLOR, defStyle.edgeColor); 352 final int windowColor = Secure.getInt( 353 cr, Secure.ACCESSIBILITY_CAPTIONING_WINDOW_COLOR, defStyle.windowColor); 354 355 String rawTypeface = Secure.getString(cr, Secure.ACCESSIBILITY_CAPTIONING_TYPEFACE); 356 if (rawTypeface == null) { 357 rawTypeface = defStyle.mRawTypeface; 358 } 359 360 return new CaptionStyle(foregroundColor, backgroundColor, edgeType, edgeColor, 361 windowColor, rawTypeface); 362 } 363 364 static { 365 WHITE_ON_BLACK = new CaptionStyle(Color.WHITE, Color.BLACK, EDGE_TYPE_NONE, 366 Color.BLACK, COLOR_NONE_OPAQUE, null); 367 BLACK_ON_WHITE = new CaptionStyle(Color.BLACK, Color.WHITE, EDGE_TYPE_NONE, 368 Color.BLACK, COLOR_NONE_OPAQUE, null); 369 YELLOW_ON_BLACK = new CaptionStyle(Color.YELLOW, Color.BLACK, EDGE_TYPE_NONE, 370 Color.BLACK, COLOR_NONE_OPAQUE, null); 371 YELLOW_ON_BLUE = new CaptionStyle(Color.YELLOW, Color.BLUE, EDGE_TYPE_NONE, 372 Color.BLACK, COLOR_NONE_OPAQUE, null); 373 374 PRESETS = new CaptionStyle[] { 375 WHITE_ON_BLACK, BLACK_ON_WHITE, YELLOW_ON_BLACK, YELLOW_ON_BLUE 376 }; 377 378 DEFAULT_CUSTOM = WHITE_ON_BLACK; 379 } 380 } 381 382 /** 383 * Listener for changes in captioning properties, including enabled state 384 * and user style preferences. 385 */ 386 public static abstract class CaptioningChangeListener { 387 /** 388 * Called when the captioning enabled state changes. 389 * 390 * @param enabled the user's new preferred captioning enabled state 391 */ 392 public void onEnabledChanged(boolean enabled) { 393 } 394 395 /** 396 * Called when the captioning user style changes. 397 * 398 * @param userStyle the user's new preferred style 399 * @see CaptioningManager#getUserStyle() 400 */ 401 public void onUserStyleChanged(CaptionStyle userStyle) { 402 } 403 404 /** 405 * Called when the captioning locale changes. 406 * 407 * @param locale the preferred captioning locale 408 * @see CaptioningManager#getLocale() 409 */ 410 public void onLocaleChanged(Locale locale) { 411 } 412 413 /** 414 * Called when the captioning font scaling factor changes. 415 * 416 * @param fontScale the preferred font scaling factor 417 * @see CaptioningManager#getFontScale() 418 */ 419 public void onFontScaleChanged(float fontScale) { 420 } 421 } 422} 423