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