diff options
author | dtrainor@chromium.org <dtrainor@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-02 19:11:09 +0000 |
---|---|---|
committer | dtrainor@chromium.org <dtrainor@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-08-02 19:11:09 +0000 |
commit | f6250339e1bd5b6b5c2bbd4a103a5087d28c0284 (patch) | |
tree | 1b6c6d534bb86ff364603afd7650dc75ba468e74 /content/public/android | |
parent | cfeb8ee8b397d98a3adad4df410be4e09852b6a1 (diff) | |
download | chromium_src-f6250339e1bd5b6b5c2bbd4a103a5087d28c0284.zip chromium_src-f6250339e1bd5b6b5c2bbd4a103a5087d28c0284.tar.gz chromium_src-f6250339e1bd5b6b5c2bbd4a103a5087d28c0284.tar.bz2 |
Start upstreaming accessibility.
BUG=http://crbug.com/138218
Review URL: https://chromiumcodereview.appspot.com/10832104
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@149667 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/public/android')
3 files changed, 570 insertions, 9 deletions
diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentView.java b/content/public/android/java/src/org/chromium/content/browser/ContentView.java index d8501ec..6e796fa 100644 --- a/content/public/android/java/src/org/chromium/content/browser/ContentView.java +++ b/content/public/android/java/src/org/chromium/content/browser/ContentView.java @@ -7,13 +7,18 @@ package org.chromium.content.browser; import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; +import android.os.Build; import android.util.AttributeSet; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.webkit.DownloadListener; import android.widget.FrameLayout; +import org.chromium.content.browser.ContentViewCore; + /** * The containing view for {@link ContentViewCore} that exists in the Android UI hierarchy and * exposes the various {@link View} functionality to it. @@ -94,21 +99,55 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal private ContentViewCore mContentViewCore; - public ContentView(Context context, int nativeWebContents, int personality) { - this(context, nativeWebContents, null, android.R.attr.webViewStyle, personality); + /** + * Creates an instance of a ContentView. + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param nativeWebContents A pointer to the native web contents. + * @param personality One of {@link #PERSONALITY_CHROME} or {@link #PERSONALITY_VIEW}. + * @return A ContentView instance. + */ + public static ContentView newInstance(Context context, int nativeWebContents, int personality) { + return newInstance(context, nativeWebContents, null, android.R.attr.webViewStyle, + personality); } - public ContentView(Context context, int nativeWebContents, AttributeSet attrs) { + /** + * Creates an instance of a ContentView. + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param nativeWebContents A pointer to the native web contents. + * @param attrs The attributes of the XML tag that is inflating the view. + * @return A ContentView instance. + */ + public static ContentView newInstance(Context context, int nativeWebContents, + AttributeSet attrs) { // TODO(klobag): use the WebViewStyle as the default style for now. It enables scrollbar. // When ContentView is moved to framework, we can define its own style in the res. - this(context, nativeWebContents, attrs, android.R.attr.webViewStyle); + return newInstance(context, nativeWebContents, attrs, android.R.attr.webViewStyle); + } + + /** + * Creates an instance of a ContentView. + * @param context The Context the view is running in, through which it can + * access the current theme, resources, etc. + * @param nativeWebContents A pointer to the native web contents. + * @param attrs The attributes of the XML tag that is inflating the view. + * @param defStyle The default style to apply to this view. + * @return A ContentView instance. + */ + public static ContentView newInstance(Context context, int nativeWebContents, + AttributeSet attrs, int defStyle) { + return newInstance(context, nativeWebContents, attrs, defStyle, PERSONALITY_VIEW); } - public ContentView(Context context, int nativeWebContents, AttributeSet attrs, int defStyle) { - this(context, nativeWebContents, attrs, defStyle, PERSONALITY_VIEW); + private static ContentView newInstance(Context context, int nativeWebContents, + AttributeSet attrs, int defStyle, int personality) { + // TODO(dtrainor): Upstream JellyBean version of AccessibilityInjector when SDK is 16. + return new ContentView(context, nativeWebContents, attrs, defStyle, personality); } - private ContentView(Context context, int nativeWebContents, AttributeSet attrs, int defStyle, + protected ContentView(Context context, int nativeWebContents, AttributeSet attrs, int defStyle, int personality) { super(context, attrs, defStyle); @@ -320,6 +359,34 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal } /** + * This method should be called when the containing activity is paused. + **/ + public void onActivityPause() { + mContentViewCore.onActivityPause(); + } + + /** + * This method should be called when the containing activity is resumed. + **/ + public void onActivityResume() { + mContentViewCore.onActivityResume(); + } + + /** + * To be called when the ContentView is shown. + **/ + public void onShow() { + mContentViewCore.onShow(); + } + + /** + * To be called when the ContentView is hidden. + **/ + public void onHide() { + mContentViewCore.onHide(); + } + + /** * Return the ContentSettings object used to control the settings for this * WebView. * @@ -363,6 +430,30 @@ public class ContentView extends FrameLayout implements ContentViewCore.Internal return super.awakenScrollBars(); } + @Override + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(info); + mContentViewCore.onInitializeAccessibilityNodeInfo(info); + } + + @Override + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + super.onInitializeAccessibilityEvent(event); + mContentViewCore.onInitializeAccessibilityEvent(event); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + mContentViewCore.onAttachedToWindow(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + mContentViewCore.onDetachedFromWindow(); + } + void updateMultiTouchZoomSupport() { mContentViewCore.updateMultiTouchZoomSupport(); } diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java index 06c035f..f1c48e9 100644 --- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java +++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java @@ -7,6 +7,7 @@ package org.chromium.content.browser; import android.content.Context; import android.content.res.Configuration; import android.graphics.Canvas; +import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.ActionMode; @@ -14,6 +15,8 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; import android.webkit.DownloadListener; import org.chromium.base.CalledByNative; @@ -25,6 +28,7 @@ import org.chromium.content.browser.ZoomManager; import org.chromium.content.common.CleanupReference; import org.chromium.content.common.TraceEvent; +import org.chromium.content.browser.accessibility.AccessibilityInjector; import org.chromium.content.browser.ContentViewGestureHandler.MotionEventDelegate; /** @@ -178,6 +182,9 @@ public class ContentViewCore implements MotionEventDelegate { // over DownloadListener. private ContentViewDownloadDelegate mDownloadDelegate; + // The AccessibilityInjector that handles loading Accessibility scripts into the web page. + private final AccessibilityInjector mAccessibilityInjector; + /** * Enable multi-process ContentView. This should be called by the application before * constructing any ContentView instances. If enabled, ContentView will run renderers in @@ -235,6 +242,9 @@ public class ContentViewCore implements MotionEventDelegate { AndroidBrowserProcess.initContentViewProcess( context, AndroidBrowserProcess.MAX_RENDERERS_SINGLE_PROCESS); + mAccessibilityInjector = AccessibilityInjector.newInstance(this); + mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary(); + initialize(context, nativeWebContents, personality); } @@ -368,6 +378,7 @@ public class ContentViewCore implements MotionEventDelegate { * omnibox can report suggestions correctly. */ public void loadUrlWithoutUrlSanitization(String url, int pageTransition) { + mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary(); if (mNativeContentViewCore != 0) { if (isPersonalityView()) { nativeLoadUrlWithoutUrlSanitizationWithUserAgentOverride( @@ -484,6 +495,7 @@ public class ContentViewCore implements MotionEventDelegate { * Reload the current page. */ public void reload() { + mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary(); if (mNativeContentViewCore != 0) nativeReload(mNativeContentViewCore); } @@ -605,19 +617,35 @@ public class ContentViewCore implements MotionEventDelegate { } /** - * This method should be called when the containing activity is paused + * This method should be called when the containing activity is paused. */ public void onActivityPause() { TraceEvent.begin(); hidePopupDialog(); + setAccessibilityState(false); TraceEvent.end(); } /** - * Called when the ContentView is hidden. + * This method should be called when the containing activity is resumed. + */ + public void onActivityResume() { + setAccessibilityState(true); + } + + /** + * To be called when the ContentView is shown. + */ + public void onShow() { + setAccessibilityState(true); + } + + /** + * To be called when the ContentView is hidden. */ public void onHide() { hidePopupDialog(); + setAccessibilityState(false); } /** @@ -645,6 +673,22 @@ public class ContentViewCore implements MotionEventDelegate { SelectPopupDialog.hide(this); } + /** + * @see View#onAttachedToWindow() + */ + @SuppressWarnings("javadoc") + protected void onAttachedToWindow() { + setAccessibilityState(true); + } + + /** + * @see View#onDetachedFromWindow() + */ + @SuppressWarnings("javadoc") + protected void onDetachedFromWindow() { + setAccessibilityState(false); + } + // End FrameLayout overrides. /** @@ -1012,6 +1056,57 @@ public class ContentViewCore implements MotionEventDelegate { getContentViewClient().onStartContentIntent(getContext(), contentUrl); } + /** + * Determines whether or not this ContentViewCore can handle this accessibility action. + * @param action The action to perform. + * @return Whether or not this action is supported. + */ + public boolean supportsAccessibilityAction(int action) { + return mAccessibilityInjector.supportsAccessibilityAction(action); + } + + /** + * Attempts to perform an accessibility action on the web content. If the accessibility action + * cannot be processed, it returns {@code null}, allowing the caller to know to call the + * super {@link View#performAccessibilityAction(int, Bundle)} method and use that return value. + * Otherwise the return value from this method should be used. + * @param action The action to perform. + * @param arguments Optional action arguments. + * @return Whether the action was performed or {@code null} if the call should be delegated to + * the super {@link View} class. + */ + public boolean performAccessibilityAction(int action, Bundle arguments) { + if (mAccessibilityInjector.supportsAccessibilityAction(action)) { + return mAccessibilityInjector.performAccessibilityAction(action, arguments); + } + + return false; + } + + /** + * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo) + */ + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { + mAccessibilityInjector.onInitializeAccessibilityNodeInfo(info); + + // TODO(dtrainor): Upstream accessibility scrolling event information once that data is + // available in ContentViewCore. Currently internal scrolling variables aren't upstreamed. + } + + /** + * @see View#onInitializeAccessibilityEvent(AccessibilityEvent) + */ + public void onInitializeAccessibilityEvent(AccessibilityEvent event) { + event.setClassName(this.getClass().getName()); + } + + /** + * Enable or disable accessibility features. + */ + public void setAccessibilityState(boolean state) { + mAccessibilityInjector.setScriptEnabled(state); + } + // The following methods are implemented at native side. /** diff --git a/content/public/android/java/src/org/chromium/content/browser/accessibility/AccessibilityInjector.java b/content/public/android/java/src/org/chromium/content/browser/accessibility/AccessibilityInjector.java new file mode 100644 index 0000000..9127ae4 --- /dev/null +++ b/content/public/android/java/src/org/chromium/content/browser/accessibility/AccessibilityInjector.java @@ -0,0 +1,375 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.content.browser.accessibility; + +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.os.SystemClock; +import android.os.Vibrator; +import android.provider.Settings; +import android.speech.tts.TextToSpeech; +import android.view.View; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.AccessibilityNodeInfo; + +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URLEncodedUtils; +import org.chromium.content.browser.ContentViewCore; +import org.json.JSONException; +import org.json.JSONObject; + +import java.lang.reflect.Field; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Responsible for accessibility injection and management of a {@link ContentViewCore}. + */ +public class AccessibilityInjector { + // The ContentView this injector is responsible for managing. + protected ContentViewCore mContentViewCore; + + // The Java objects that are exposed to JavaScript + private TextToSpeechWrapper mTextToSpeech; + private VibratorWrapper mVibrator; + + // Lazily loaded helper objects. + private AccessibilityManager mAccessibilityManager; + + // Whether or not we should be injecting the script. + protected boolean mInjectedScriptEnabled; + protected boolean mScriptInjected; + + // constants for determining script injection strategy + private static final int ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED = -1; + private static final int ACCESSIBILITY_SCRIPT_INJECTION_OPTED_OUT = 0; + private static final int ACCESSIBILITY_SCRIPT_INJECTION_PROVIDED = 1; + private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE = "accessibility"; + private static final String ALIAS_ACCESSIBILITY_JS_INTERFACE_2 = "accessibility2"; + + // Template for JavaScript that injects a screen-reader. + private static final String ACCESSIBILITY_SCREEN_READER_URL = + "https://ssl.gstatic.com/accessibility/javascript/android/chromeandroidvox.js"; + + private static final String ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE = + "(function() {" + + " var chooser = document.createElement('script');" + + " chooser.type = 'text/javascript';" + + " chooser.src = '%1s';" + + " document.getElementsByTagName('head')[0].appendChild(chooser);" + + " })();"; + + // JavaScript call to turn ChromeVox on or off. + private static final String TOGGLE_CHROME_VOX_JAVASCRIPT = + "(function() {" + + " if (typeof cvox !== 'undefined') {" + + " cvox.ChromeVox.host.activateOrDeactivateChromeVox(%1s);" + + " }" + + " })();"; + + /** + * Returns an instance of the {@link AccessibilityInjector} based on the SDK version. + * @param view The ContentViewCore that this AccessibilityInjector manages. + * @return An instance of a {@link AccessibilityInjector}. + */ + public static AccessibilityInjector newInstance(ContentViewCore view) { + // TODO(dtrainor): Upstream JellyBean version of AccessibilityInjector when SDK is 16. + return new AccessibilityInjector(view); + } + + /** + * Creates an instance of the IceCreamSandwichAccessibilityInjector. + * @param view The ContentViewCore that this AccessibilityInjector manages. + */ + protected AccessibilityInjector(ContentViewCore view) { + mContentViewCore = view; + } + + /** + * Injects a <script> tag into the current web site that pulls in the ChromeVox script for + * accessibility support. Only injects if accessibility is turned on by + * {@link AccessibilityManager#isEnabled()}, accessibility script injection is turned on, and + * javascript is enabled on this page. + * + * @see AccessibilityManager#isEnabled() + */ + public void injectAccessibilityScriptIntoPage() { + if (!accessibilityIsAvailable()) return; + + int axsParameterValue = getAxsUrlParameterValue(); + if (axsParameterValue == ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED) { + try { + Field field = Settings.Secure.class.getField("ACCESSIBILITY_SCRIPT_INJECTION"); + field.setAccessible(true); + String ACCESSIBILITY_SCRIPT_INJECTION = (String) field.get(null); + + boolean onDeviceScriptInjectionEnabled = (Settings.Secure.getInt( + mContentViewCore.getContext().getContentResolver(), + ACCESSIBILITY_SCRIPT_INJECTION, 0) == 1); + String js = getScreenReaderInjectingJs(); + + if (onDeviceScriptInjectionEnabled && js != null && mContentViewCore.isAlive()) { + addOrRemoveAccessibilityApisIfNecessary(); + mContentViewCore.evaluateJavaScript(js); + mInjectedScriptEnabled = true; + mScriptInjected = true; + } + } catch (NoSuchFieldException ex) { + } catch (IllegalArgumentException ex) { + } catch (IllegalAccessException ex) { + } + } + } + + /** + * Handles adding or removing accessibility related Java objects ({@link TextToSpeech} and + * {@link Vibrator}) interfaces from Javascript. This method should be called at a time when it + * is safe to add or remove these interfaces, specifically when the {@link ContentView} is first + * initialized or right before the {@link ContentView} is about to navigate to a URL or reload. + * <p> + * If this method is called at other times, the interfaces might not be correctly removed, + * meaning that Javascript can still access these Java objects that may have been already + * shut down. + */ + public void addOrRemoveAccessibilityApisIfNecessary() { + if (accessibilityIsAvailable()) { + addAccessibilityApis(); + } else { + removeAccessibilityApis(); + } + } + + /** + * Checks whether or not touch to explore is enabled on the system. + */ + public boolean accessibilityIsAvailable() { + return getAccessibilityManager().isEnabled() && + mContentViewCore.getContentSettings() != null && + mContentViewCore.getContentSettings().getJavaScriptEnabled(); + } + + /** + * Sets whether or not the script is enabled. If the script is disabled, we also stop any + * we output that is occurring. + * @param enabled Whether or not to enable the script. + */ + public void setScriptEnabled(boolean enabled) { + if (!accessibilityIsAvailable() || mInjectedScriptEnabled == enabled) return; + + mInjectedScriptEnabled = enabled; + if (mContentViewCore.isAlive()) { + String js = String.format(TOGGLE_CHROME_VOX_JAVASCRIPT, Boolean.toString( + mInjectedScriptEnabled)); + mContentViewCore.evaluateJavaScript(js); + + if (!mInjectedScriptEnabled) { + // Stop any TTS/Vibration right now. + onPageLostFocus(); + } + } + } + + /** + * Notifies this handler that a page load has started, which means we should mark the + * accessibility script as not being injected. This way we can properly ignore incoming + * accessibility gesture events. + */ + public void onPageLoadStarted() { + mScriptInjected = false; + } + + /** + * Stop any notifications that are currently going on (e.g. Text-to-Speech). + */ + public void onPageLostFocus() { + if (mContentViewCore.isAlive()) { + if (mTextToSpeech != null) mTextToSpeech.stop(); + if (mVibrator != null) mVibrator.cancel(); + } + } + + /** + * Initializes an {@link AccessibilityNodeInfo} with the actions and movement granularity + * levels supported by this {@link AccessibilityInjector}. + * <p> + * If an action identifier is added in this method, this {@link AccessibilityInjector} should + * also return {@code true} from {@link #supportsAccessibilityAction(int)}. + * </p> + * + * @param info The info to initialize. + * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo) + */ + public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { } + + /** + * Returns {@code true} if this {@link AccessibilityInjector} should handle the specified + * action. + * + * @param action An accessibility action identifier. + * @return {@code true} if this {@link AccessibilityInjector} should handle the specified + * action. + */ + public boolean supportsAccessibilityAction(int action) { + return false; + } + + /** + * Performs the specified accessibility action. + * + * @param action The identifier of the action to perform. + * @param arguments The action arguments, or {@code null} if no arguments. + * @return {@code true} if the action was successful. + * @see View#performAccessibilityAction(int, Bundle) + */ + public boolean performAccessibilityAction(int action, Bundle arguments) { + return false; + } + + protected void addAccessibilityApis() { + Context context = mContentViewCore.getContext(); + if (context != null) { + // Enabled, we should try to add if we have to. + if (mTextToSpeech == null) { + mTextToSpeech = new TextToSpeechWrapper(context); + mContentViewCore.addJavascriptInterface(mTextToSpeech, + ALIAS_ACCESSIBILITY_JS_INTERFACE, false); + } + + if (mVibrator == null) { + mVibrator = new VibratorWrapper(context); + mContentViewCore.addJavascriptInterface(mVibrator, + ALIAS_ACCESSIBILITY_JS_INTERFACE_2, false); + } + } + } + + protected void removeAccessibilityApis() { + if (mTextToSpeech != null) { + mContentViewCore.removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE); + mTextToSpeech.stop(); + mTextToSpeech.shutdownInternal(); + mTextToSpeech = null; + } + + if (mVibrator != null) { + mContentViewCore.removeJavascriptInterface(ALIAS_ACCESSIBILITY_JS_INTERFACE_2); + mVibrator.cancel(); + mVibrator = null; + } + } + + private int getAxsUrlParameterValue() { + if (mContentViewCore.getUrl() == null) return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED; + + try { + List<NameValuePair> params = URLEncodedUtils.parse(new URI(mContentViewCore.getUrl()), + null); + + for (NameValuePair param : params) { + if ("axs".equals(param.getName())) { + return Integer.parseInt(param.getValue()); + } + } + } catch (URISyntaxException ex) { + } catch (NumberFormatException ex) { + } catch (IllegalArgumentException ex) { + } + + return ACCESSIBILITY_SCRIPT_INJECTION_UNDEFINED; + } + + private String getScreenReaderInjectingJs() { + return String.format(ACCESSIBILITY_SCREEN_READER_JAVASCRIPT_TEMPLATE, + ACCESSIBILITY_SCREEN_READER_URL); + } + + private AccessibilityManager getAccessibilityManager() { + if (mAccessibilityManager == null) { + mAccessibilityManager = (AccessibilityManager) mContentViewCore.getContext(). + getSystemService(Context.ACCESSIBILITY_SERVICE); + } + + return mAccessibilityManager; + } + + /** + * Used to protect how long JavaScript can vibrate for. This isn't a good comprehensive + * protection, just used to cover mistakes and protect against long vibrate durations/repeats. + * + * Also only exposes methods we *want* to expose, no others for the class. + */ + private static class VibratorWrapper { + private static final long MAX_VIBRATE_DURATION_MS = 5000; + + private Vibrator mVibrator; + + public VibratorWrapper(Context context) { + mVibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); + } + + @SuppressWarnings("unused") + public boolean hasVibrator() { + return mVibrator.hasVibrator(); + } + + @SuppressWarnings("unused") + public void vibrate(long milliseconds) { + milliseconds = Math.min(milliseconds, MAX_VIBRATE_DURATION_MS); + mVibrator.vibrate(milliseconds); + } + + @SuppressWarnings("unused") + public void vibrate(long[] pattern, int repeat) { + for (int i = 0; i < pattern.length; ++i) { + pattern[i] = Math.min(pattern[i], MAX_VIBRATE_DURATION_MS); + } + + repeat = -1; + + mVibrator.vibrate(pattern, repeat); + } + + @SuppressWarnings("unused") + public void cancel() { + mVibrator.cancel(); + } + } + + /** + * Used to protect the TextToSpeech class, only exposing the methods we want to expose. + */ + private static class TextToSpeechWrapper { + private TextToSpeech mTextToSpeech; + + public TextToSpeechWrapper(Context context) { + mTextToSpeech = new TextToSpeech(context, null, null); + } + + @SuppressWarnings("unused") + public boolean isSpeaking() { + return mTextToSpeech.isSpeaking(); + } + + @SuppressWarnings("unused") + public int speak(String text, int queueMode, HashMap<String, String> params) { + return mTextToSpeech.speak(text, queueMode, params); + } + + @SuppressWarnings("unused") + public int stop() { + return mTextToSpeech.stop(); + } + + @SuppressWarnings("unused") + protected void shutdownInternal() { + mTextToSpeech.shutdown(); + } + } +} |