Loading core/java/android/webkit/AccessibilityInjector.java +115 −48 Original line number Original line Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.webkit; import android.content.Context; import android.content.Context; import android.os.Bundle; import android.os.Bundle; import android.os.Handler; import android.os.SystemClock; import android.os.SystemClock; import android.provider.Settings; import android.provider.Settings; import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech; Loading Loading @@ -159,7 +160,7 @@ class AccessibilityInjector { * <p> * <p> * This should only be called before a page loads. * This should only be called before a page loads. */ */ private void addAccessibilityApisIfNecessary() { public void addAccessibilityApisIfNecessary() { if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) { if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) { return; return; } } Loading Loading @@ -333,8 +334,9 @@ class AccessibilityInjector { */ */ public void onPageStarted(String url) { public void onPageStarted(String url) { mAccessibilityScriptInjected = false; mAccessibilityScriptInjected = false; if (DEBUG) if (DEBUG) { Log.w(TAG, "[" + mWebView.hashCode() + "] Started loading new page"); Log.w(TAG, "[" + mWebView.hashCode() + "] Started loading new page"); } addAccessibilityApisIfNecessary(); addAccessibilityApisIfNecessary(); } } Loading @@ -348,32 +350,59 @@ class AccessibilityInjector { */ */ public void onPageFinished(String url) { public void onPageFinished(String url) { if (!isAccessibilityEnabled()) { if (!isAccessibilityEnabled()) { mAccessibilityScriptInjected = false; toggleFallbackAccessibilityInjector(false); toggleFallbackAccessibilityInjector(false); return; return; } } if (!shouldInjectJavaScript(url)) { mAccessibilityScriptInjected = false; toggleFallbackAccessibilityInjector(true); toggleFallbackAccessibilityInjector(true); if (DEBUG) Log.d(TAG, "[" + mWebView.hashCode() + "] Using fallback accessibility support"); if (shouldInjectJavaScript(url)) { return; // If we're supposed to use the JS screen reader, request a // callback to confirm that CallbackHandler is working. if (DEBUG) { Log.d(TAG, "[" + mWebView.hashCode() + "] Request callback "); } } mCallback.requestCallback(mWebView, mInjectScriptRunnable); } } /** * Runnable used to inject the JavaScript-based screen reader if the * {@link CallbackHandler} API was successfully exposed to JavaScript. */ private Runnable mInjectScriptRunnable = new Runnable() { @Override public void run() { if (DEBUG) { Log.d(TAG, "[" + mWebView.hashCode() + "] Received callback"); } injectJavaScript(); } }; /** * Called by {@link #mInjectScriptRunnable} to inject the JavaScript-based * screen reader after confirming that the {@link CallbackHandler} API is * functional. */ private void injectJavaScript() { toggleFallbackAccessibilityInjector(false); toggleFallbackAccessibilityInjector(false); if (!mAccessibilityScriptInjected) { if (!mAccessibilityScriptInjected) { mAccessibilityScriptInjected = true; mAccessibilityScriptInjected = true; final String injectionUrl = getScreenReaderInjectionUrl(); final String injectionUrl = getScreenReaderInjectionUrl(); mWebView.loadUrl(injectionUrl); mWebView.loadUrl(injectionUrl); if (DEBUG) if (DEBUG) { Log.d(TAG, "[" + mWebView.hashCode() + "] Loading screen reader into WebView"); Log.d(TAG, "[" + mWebView.hashCode() + "] Loading screen reader into WebView"); } } else { } else { if (DEBUG) if (DEBUG) { Log.w(TAG, "[" + mWebView.hashCode() + "] Attempted to inject screen reader twice"); Log.w(TAG, "[" + mWebView.hashCode() + "] Attempted to inject screen reader twice"); } } } } } /** /** * Adjusts the accessibility injection state to reflect changes in the * Adjusts the accessibility injection state to reflect changes in the Loading Loading @@ -447,12 +476,10 @@ class AccessibilityInjector { * been done. * been done. */ */ private void addTtsApis() { private void addTtsApis() { if (mTextToSpeech != null) { if (mTextToSpeech == null) { return; } if (DEBUG) Log.d(TAG, "[" + mWebView.hashCode() + "] Adding TTS APIs into WebView"); mTextToSpeech = new TextToSpeechWrapper(mContext); mTextToSpeech = new TextToSpeechWrapper(mContext); } mWebView.addJavascriptInterface(mTextToSpeech, ALIAS_TTS_JS_INTERFACE); mWebView.addJavascriptInterface(mTextToSpeech, ALIAS_TTS_JS_INTERFACE); } } Loading @@ -461,34 +488,29 @@ class AccessibilityInjector { * already been done. * already been done. */ */ private void removeTtsApis() { private void removeTtsApis() { if (mTextToSpeech == null) { if (mTextToSpeech != null) { return; } if (DEBUG) Log.d(TAG, "[" + mWebView.hashCode() + "] Removing TTS APIs from WebView"); mWebView.removeJavascriptInterface(ALIAS_TTS_JS_INTERFACE); mTextToSpeech.stop(); mTextToSpeech.stop(); mTextToSpeech.shutdown(); mTextToSpeech.shutdown(); mTextToSpeech = null; mTextToSpeech = null; } } private void addCallbackApis() { mWebView.removeJavascriptInterface(ALIAS_TTS_JS_INTERFACE); if (mCallback != null) { return; } } private void addCallbackApis() { if (mCallback == null) { mCallback = new CallbackHandler(ALIAS_TRAVERSAL_JS_INTERFACE); mCallback = new CallbackHandler(ALIAS_TRAVERSAL_JS_INTERFACE); } mWebView.addJavascriptInterface(mCallback, ALIAS_TRAVERSAL_JS_INTERFACE); mWebView.addJavascriptInterface(mCallback, ALIAS_TRAVERSAL_JS_INTERFACE); } } private void removeCallbackApis() { private void removeCallbackApis() { if (mCallback == null) { if (mCallback != null) { return; mCallback = null; } } mWebView.removeJavascriptInterface(ALIAS_TRAVERSAL_JS_INTERFACE); mWebView.removeJavascriptInterface(ALIAS_TRAVERSAL_JS_INTERFACE); mCallback = null; } } /** /** Loading Loading @@ -638,9 +660,10 @@ class AccessibilityInjector { private volatile boolean mShutdown; private volatile boolean mShutdown; public TextToSpeechWrapper(Context context) { public TextToSpeechWrapper(Context context) { if (DEBUG) if (DEBUG) { Log.d(WRAP_TAG, "[" + hashCode() + "] Initializing text-to-speech on thread " Log.d(WRAP_TAG, "[" + hashCode() + "] Initializing text-to-speech on thread " + Thread.currentThread().getId() + "..."); + Thread.currentThread().getId() + "..."); } final String pkgName = context.getPackageName(); final String pkgName = context.getPackageName(); Loading Loading @@ -672,13 +695,15 @@ class AccessibilityInjector { public int speak(String text, int queueMode, HashMap<String, String> params) { public int speak(String text, int queueMode, HashMap<String, String> params) { synchronized (mTextToSpeech) { synchronized (mTextToSpeech) { if (!mReady) { if (!mReady) { if (DEBUG) if (DEBUG) { Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to speak before TTS init"); Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to speak before TTS init"); } return TextToSpeech.ERROR; return TextToSpeech.ERROR; } else { } else { if (DEBUG) if (DEBUG) { Log.i(WRAP_TAG, "[" + hashCode() + "] Speak called from JS binder"); Log.i(WRAP_TAG, "[" + hashCode() + "] Speak called from JS binder"); } } } return mTextToSpeech.speak(text, queueMode, params); return mTextToSpeech.speak(text, queueMode, params); } } Loading @@ -689,13 +714,15 @@ class AccessibilityInjector { public int stop() { public int stop() { synchronized (mTextToSpeech) { synchronized (mTextToSpeech) { if (!mReady) { if (!mReady) { if (DEBUG) if (DEBUG) { Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to stop before initialize"); Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to stop before initialize"); } return TextToSpeech.ERROR; return TextToSpeech.ERROR; } else { } else { if (DEBUG) if (DEBUG) { Log.i(WRAP_TAG, "[" + hashCode() + "] Stop called from JS binder"); Log.i(WRAP_TAG, "[" + hashCode() + "] Stop called from JS binder"); } } } return mTextToSpeech.stop(); return mTextToSpeech.stop(); } } Loading @@ -705,13 +732,15 @@ class AccessibilityInjector { protected void shutdown() { protected void shutdown() { synchronized (mTextToSpeech) { synchronized (mTextToSpeech) { if (!mReady) { if (!mReady) { if (DEBUG) if (DEBUG) { Log.w(WRAP_TAG, "[" + hashCode() + "] Called shutdown before initialize"); Log.w(WRAP_TAG, "[" + hashCode() + "] Called shutdown before initialize"); } } else { } else { if (DEBUG) if (DEBUG) { Log.i(WRAP_TAG, "[" + hashCode() + "] Shutting down text-to-speech from " Log.i(WRAP_TAG, "[" + hashCode() + "] Shutting down text-to-speech from " + "thread " + Thread.currentThread().getId() + "..."); + "thread " + Thread.currentThread().getId() + "..."); } } } mShutdown = true; mShutdown = true; mReady = false; mReady = false; mTextToSpeech.shutdown(); mTextToSpeech.shutdown(); Loading @@ -723,14 +752,16 @@ class AccessibilityInjector { public void onInit(int status) { public void onInit(int status) { synchronized (mTextToSpeech) { synchronized (mTextToSpeech) { if (!mShutdown && (status == TextToSpeech.SUCCESS)) { if (!mShutdown && (status == TextToSpeech.SUCCESS)) { if (DEBUG) if (DEBUG) { Log.d(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode() Log.d(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode() + "] Initialized successfully"); + "] Initialized successfully"); } mReady = true; mReady = true; } else { } else { if (DEBUG) if (DEBUG) { Log.w(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode() Log.w(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode() + "] Failed to initialize"); + "] Failed to initialize"); } mReady = false; mReady = false; } } } } Loading @@ -745,10 +776,11 @@ class AccessibilityInjector { @Override @Override public void onError(String utteranceId) { public void onError(String utteranceId) { if (DEBUG) if (DEBUG) { Log.w(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode() Log.w(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode() + "] Failed to speak utterance"); + "] Failed to speak utterance"); } } } @Override @Override public void onDone(String utteranceId) { public void onDone(String utteranceId) { Loading @@ -770,12 +802,16 @@ class AccessibilityInjector { private final AtomicInteger mResultIdCounter = new AtomicInteger(); private final AtomicInteger mResultIdCounter = new AtomicInteger(); private final Object mResultLock = new Object(); private final Object mResultLock = new Object(); private final String mInterfaceName; private final String mInterfaceName; private final Handler mMainHandler; private Runnable mCallbackRunnable; private boolean mResult = false; private boolean mResult = false; private int mResultId = -1; private int mResultId = -1; private CallbackHandler(String interfaceName) { private CallbackHandler(String interfaceName) { mInterfaceName = interfaceName; mInterfaceName = interfaceName; mMainHandler = new Handler(); } } /** /** Loading Loading @@ -826,25 +862,29 @@ class AccessibilityInjector { private boolean waitForResultTimedLocked(int resultId) { private boolean waitForResultTimedLocked(int resultId) { final long startTimeMillis = SystemClock.uptimeMillis(); final long startTimeMillis = SystemClock.uptimeMillis(); if (DEBUG) if (DEBUG) { Log.d(TAG, "Waiting for CVOX result with ID " + resultId + "..."); Log.d(TAG, "Waiting for CVOX result with ID " + resultId + "..."); } while (true) { while (true) { // Fail if we received a callback from the future. // Fail if we received a callback from the future. if (mResultId > resultId) { if (mResultId > resultId) { if (DEBUG) if (DEBUG) { Log.w(TAG, "Aborted CVOX result"); Log.w(TAG, "Aborted CVOX result"); } return false; return false; } } final long elapsedTimeMillis = (SystemClock.uptimeMillis() - startTimeMillis); final long elapsedTimeMillis = (SystemClock.uptimeMillis() - startTimeMillis); // Succeed if we received the callback we were expecting. // Succeed if we received the callback we were expecting. if (DEBUG) if (DEBUG) { Log.w(TAG, "Check " + mResultId + " versus expected " + resultId); Log.w(TAG, "Check " + mResultId + " versus expected " + resultId); } if (mResultId == resultId) { if (mResultId == resultId) { if (DEBUG) if (DEBUG) { Log.w(TAG, "Received CVOX result after " + elapsedTimeMillis + " ms"); Log.w(TAG, "Received CVOX result after " + elapsedTimeMillis + " ms"); } return true; return true; } } Loading @@ -852,21 +892,24 @@ class AccessibilityInjector { // Fail if we've already exceeded the timeout. // Fail if we've already exceeded the timeout. if (waitTimeMillis <= 0) { if (waitTimeMillis <= 0) { if (DEBUG) if (DEBUG) { Log.w(TAG, "Timed out while waiting for CVOX result"); Log.w(TAG, "Timed out while waiting for CVOX result"); } return false; return false; } } try { try { if (DEBUG) if (DEBUG) { Log.w(TAG, "Start waiting..."); Log.w(TAG, "Start waiting..."); } mResultLock.wait(waitTimeMillis); mResultLock.wait(waitTimeMillis); } catch (InterruptedException ie) { } catch (InterruptedException ie) { if (DEBUG) if (DEBUG) { Log.w(TAG, "Interrupted while waiting for CVOX result"); Log.w(TAG, "Interrupted while waiting for CVOX result"); } } } } } } } /** /** * Callback exposed to JavaScript. Handles returning the result of a * Callback exposed to JavaScript. Handles returning the result of a Loading @@ -878,8 +921,9 @@ class AccessibilityInjector { @JavascriptInterface @JavascriptInterface @SuppressWarnings("unused") @SuppressWarnings("unused") public void onResult(String id, String result) { public void onResult(String id, String result) { if (DEBUG) if (DEBUG) { Log.w(TAG, "Saw CVOX result of '" + result + "' for ID " + id); Log.w(TAG, "Saw CVOX result of '" + result + "' for ID " + id); } final int resultId; final int resultId; try { try { Loading @@ -893,11 +937,34 @@ class AccessibilityInjector { mResult = Boolean.parseBoolean(result); mResult = Boolean.parseBoolean(result); mResultId = resultId; mResultId = resultId; } else { } else { if (DEBUG) if (DEBUG) { Log.w(TAG, "Result with ID " + resultId + " was stale vesus " + mResultId); Log.w(TAG, "Result with ID " + resultId + " was stale vesus " + mResultId); } } } mResultLock.notifyAll(); mResultLock.notifyAll(); } } } } /** * Requests a callback to ensure that the JavaScript interface for this * object has been added successfully. * * @param webView The web view to request a callback from. * @param callbackRunnable Runnable to execute if a callback is received. */ public void requestCallback(WebView webView, Runnable callbackRunnable) { mCallbackRunnable = callbackRunnable; webView.loadUrl("javascript:(function() { " + mInterfaceName + ".callback(); })();"); } @JavascriptInterface @SuppressWarnings("unused") public void callback() { if (mCallbackRunnable != null) { mMainHandler.post(mCallbackRunnable); mCallbackRunnable = null; } } } } } } core/java/android/webkit/WebViewClassic.java +3 −0 Original line number Original line Diff line number Diff line Loading @@ -2500,6 +2500,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // Remove all pending messages because we are restoring previous // Remove all pending messages because we are restoring previous // state. // state. mWebViewCore.removeMessages(); mWebViewCore.removeMessages(); if (isAccessibilityInjectionEnabled()) { getAccessibilityInjector().addAccessibilityApisIfNecessary(); } // Send a restore state message. // Send a restore state message. mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index); mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index); } } Loading Loading
core/java/android/webkit/AccessibilityInjector.java +115 −48 Original line number Original line Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.webkit; import android.content.Context; import android.content.Context; import android.os.Bundle; import android.os.Bundle; import android.os.Handler; import android.os.SystemClock; import android.os.SystemClock; import android.provider.Settings; import android.provider.Settings; import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech; Loading Loading @@ -159,7 +160,7 @@ class AccessibilityInjector { * <p> * <p> * This should only be called before a page loads. * This should only be called before a page loads. */ */ private void addAccessibilityApisIfNecessary() { public void addAccessibilityApisIfNecessary() { if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) { if (!isAccessibilityEnabled() || !isJavaScriptEnabled()) { return; return; } } Loading Loading @@ -333,8 +334,9 @@ class AccessibilityInjector { */ */ public void onPageStarted(String url) { public void onPageStarted(String url) { mAccessibilityScriptInjected = false; mAccessibilityScriptInjected = false; if (DEBUG) if (DEBUG) { Log.w(TAG, "[" + mWebView.hashCode() + "] Started loading new page"); Log.w(TAG, "[" + mWebView.hashCode() + "] Started loading new page"); } addAccessibilityApisIfNecessary(); addAccessibilityApisIfNecessary(); } } Loading @@ -348,32 +350,59 @@ class AccessibilityInjector { */ */ public void onPageFinished(String url) { public void onPageFinished(String url) { if (!isAccessibilityEnabled()) { if (!isAccessibilityEnabled()) { mAccessibilityScriptInjected = false; toggleFallbackAccessibilityInjector(false); toggleFallbackAccessibilityInjector(false); return; return; } } if (!shouldInjectJavaScript(url)) { mAccessibilityScriptInjected = false; toggleFallbackAccessibilityInjector(true); toggleFallbackAccessibilityInjector(true); if (DEBUG) Log.d(TAG, "[" + mWebView.hashCode() + "] Using fallback accessibility support"); if (shouldInjectJavaScript(url)) { return; // If we're supposed to use the JS screen reader, request a // callback to confirm that CallbackHandler is working. if (DEBUG) { Log.d(TAG, "[" + mWebView.hashCode() + "] Request callback "); } } mCallback.requestCallback(mWebView, mInjectScriptRunnable); } } /** * Runnable used to inject the JavaScript-based screen reader if the * {@link CallbackHandler} API was successfully exposed to JavaScript. */ private Runnable mInjectScriptRunnable = new Runnable() { @Override public void run() { if (DEBUG) { Log.d(TAG, "[" + mWebView.hashCode() + "] Received callback"); } injectJavaScript(); } }; /** * Called by {@link #mInjectScriptRunnable} to inject the JavaScript-based * screen reader after confirming that the {@link CallbackHandler} API is * functional. */ private void injectJavaScript() { toggleFallbackAccessibilityInjector(false); toggleFallbackAccessibilityInjector(false); if (!mAccessibilityScriptInjected) { if (!mAccessibilityScriptInjected) { mAccessibilityScriptInjected = true; mAccessibilityScriptInjected = true; final String injectionUrl = getScreenReaderInjectionUrl(); final String injectionUrl = getScreenReaderInjectionUrl(); mWebView.loadUrl(injectionUrl); mWebView.loadUrl(injectionUrl); if (DEBUG) if (DEBUG) { Log.d(TAG, "[" + mWebView.hashCode() + "] Loading screen reader into WebView"); Log.d(TAG, "[" + mWebView.hashCode() + "] Loading screen reader into WebView"); } } else { } else { if (DEBUG) if (DEBUG) { Log.w(TAG, "[" + mWebView.hashCode() + "] Attempted to inject screen reader twice"); Log.w(TAG, "[" + mWebView.hashCode() + "] Attempted to inject screen reader twice"); } } } } } /** /** * Adjusts the accessibility injection state to reflect changes in the * Adjusts the accessibility injection state to reflect changes in the Loading Loading @@ -447,12 +476,10 @@ class AccessibilityInjector { * been done. * been done. */ */ private void addTtsApis() { private void addTtsApis() { if (mTextToSpeech != null) { if (mTextToSpeech == null) { return; } if (DEBUG) Log.d(TAG, "[" + mWebView.hashCode() + "] Adding TTS APIs into WebView"); mTextToSpeech = new TextToSpeechWrapper(mContext); mTextToSpeech = new TextToSpeechWrapper(mContext); } mWebView.addJavascriptInterface(mTextToSpeech, ALIAS_TTS_JS_INTERFACE); mWebView.addJavascriptInterface(mTextToSpeech, ALIAS_TTS_JS_INTERFACE); } } Loading @@ -461,34 +488,29 @@ class AccessibilityInjector { * already been done. * already been done. */ */ private void removeTtsApis() { private void removeTtsApis() { if (mTextToSpeech == null) { if (mTextToSpeech != null) { return; } if (DEBUG) Log.d(TAG, "[" + mWebView.hashCode() + "] Removing TTS APIs from WebView"); mWebView.removeJavascriptInterface(ALIAS_TTS_JS_INTERFACE); mTextToSpeech.stop(); mTextToSpeech.stop(); mTextToSpeech.shutdown(); mTextToSpeech.shutdown(); mTextToSpeech = null; mTextToSpeech = null; } } private void addCallbackApis() { mWebView.removeJavascriptInterface(ALIAS_TTS_JS_INTERFACE); if (mCallback != null) { return; } } private void addCallbackApis() { if (mCallback == null) { mCallback = new CallbackHandler(ALIAS_TRAVERSAL_JS_INTERFACE); mCallback = new CallbackHandler(ALIAS_TRAVERSAL_JS_INTERFACE); } mWebView.addJavascriptInterface(mCallback, ALIAS_TRAVERSAL_JS_INTERFACE); mWebView.addJavascriptInterface(mCallback, ALIAS_TRAVERSAL_JS_INTERFACE); } } private void removeCallbackApis() { private void removeCallbackApis() { if (mCallback == null) { if (mCallback != null) { return; mCallback = null; } } mWebView.removeJavascriptInterface(ALIAS_TRAVERSAL_JS_INTERFACE); mWebView.removeJavascriptInterface(ALIAS_TRAVERSAL_JS_INTERFACE); mCallback = null; } } /** /** Loading Loading @@ -638,9 +660,10 @@ class AccessibilityInjector { private volatile boolean mShutdown; private volatile boolean mShutdown; public TextToSpeechWrapper(Context context) { public TextToSpeechWrapper(Context context) { if (DEBUG) if (DEBUG) { Log.d(WRAP_TAG, "[" + hashCode() + "] Initializing text-to-speech on thread " Log.d(WRAP_TAG, "[" + hashCode() + "] Initializing text-to-speech on thread " + Thread.currentThread().getId() + "..."); + Thread.currentThread().getId() + "..."); } final String pkgName = context.getPackageName(); final String pkgName = context.getPackageName(); Loading Loading @@ -672,13 +695,15 @@ class AccessibilityInjector { public int speak(String text, int queueMode, HashMap<String, String> params) { public int speak(String text, int queueMode, HashMap<String, String> params) { synchronized (mTextToSpeech) { synchronized (mTextToSpeech) { if (!mReady) { if (!mReady) { if (DEBUG) if (DEBUG) { Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to speak before TTS init"); Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to speak before TTS init"); } return TextToSpeech.ERROR; return TextToSpeech.ERROR; } else { } else { if (DEBUG) if (DEBUG) { Log.i(WRAP_TAG, "[" + hashCode() + "] Speak called from JS binder"); Log.i(WRAP_TAG, "[" + hashCode() + "] Speak called from JS binder"); } } } return mTextToSpeech.speak(text, queueMode, params); return mTextToSpeech.speak(text, queueMode, params); } } Loading @@ -689,13 +714,15 @@ class AccessibilityInjector { public int stop() { public int stop() { synchronized (mTextToSpeech) { synchronized (mTextToSpeech) { if (!mReady) { if (!mReady) { if (DEBUG) if (DEBUG) { Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to stop before initialize"); Log.w(WRAP_TAG, "[" + hashCode() + "] Attempted to stop before initialize"); } return TextToSpeech.ERROR; return TextToSpeech.ERROR; } else { } else { if (DEBUG) if (DEBUG) { Log.i(WRAP_TAG, "[" + hashCode() + "] Stop called from JS binder"); Log.i(WRAP_TAG, "[" + hashCode() + "] Stop called from JS binder"); } } } return mTextToSpeech.stop(); return mTextToSpeech.stop(); } } Loading @@ -705,13 +732,15 @@ class AccessibilityInjector { protected void shutdown() { protected void shutdown() { synchronized (mTextToSpeech) { synchronized (mTextToSpeech) { if (!mReady) { if (!mReady) { if (DEBUG) if (DEBUG) { Log.w(WRAP_TAG, "[" + hashCode() + "] Called shutdown before initialize"); Log.w(WRAP_TAG, "[" + hashCode() + "] Called shutdown before initialize"); } } else { } else { if (DEBUG) if (DEBUG) { Log.i(WRAP_TAG, "[" + hashCode() + "] Shutting down text-to-speech from " Log.i(WRAP_TAG, "[" + hashCode() + "] Shutting down text-to-speech from " + "thread " + Thread.currentThread().getId() + "..."); + "thread " + Thread.currentThread().getId() + "..."); } } } mShutdown = true; mShutdown = true; mReady = false; mReady = false; mTextToSpeech.shutdown(); mTextToSpeech.shutdown(); Loading @@ -723,14 +752,16 @@ class AccessibilityInjector { public void onInit(int status) { public void onInit(int status) { synchronized (mTextToSpeech) { synchronized (mTextToSpeech) { if (!mShutdown && (status == TextToSpeech.SUCCESS)) { if (!mShutdown && (status == TextToSpeech.SUCCESS)) { if (DEBUG) if (DEBUG) { Log.d(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode() Log.d(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode() + "] Initialized successfully"); + "] Initialized successfully"); } mReady = true; mReady = true; } else { } else { if (DEBUG) if (DEBUG) { Log.w(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode() Log.w(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode() + "] Failed to initialize"); + "] Failed to initialize"); } mReady = false; mReady = false; } } } } Loading @@ -745,10 +776,11 @@ class AccessibilityInjector { @Override @Override public void onError(String utteranceId) { public void onError(String utteranceId) { if (DEBUG) if (DEBUG) { Log.w(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode() Log.w(WRAP_TAG, "[" + TextToSpeechWrapper.this.hashCode() + "] Failed to speak utterance"); + "] Failed to speak utterance"); } } } @Override @Override public void onDone(String utteranceId) { public void onDone(String utteranceId) { Loading @@ -770,12 +802,16 @@ class AccessibilityInjector { private final AtomicInteger mResultIdCounter = new AtomicInteger(); private final AtomicInteger mResultIdCounter = new AtomicInteger(); private final Object mResultLock = new Object(); private final Object mResultLock = new Object(); private final String mInterfaceName; private final String mInterfaceName; private final Handler mMainHandler; private Runnable mCallbackRunnable; private boolean mResult = false; private boolean mResult = false; private int mResultId = -1; private int mResultId = -1; private CallbackHandler(String interfaceName) { private CallbackHandler(String interfaceName) { mInterfaceName = interfaceName; mInterfaceName = interfaceName; mMainHandler = new Handler(); } } /** /** Loading Loading @@ -826,25 +862,29 @@ class AccessibilityInjector { private boolean waitForResultTimedLocked(int resultId) { private boolean waitForResultTimedLocked(int resultId) { final long startTimeMillis = SystemClock.uptimeMillis(); final long startTimeMillis = SystemClock.uptimeMillis(); if (DEBUG) if (DEBUG) { Log.d(TAG, "Waiting for CVOX result with ID " + resultId + "..."); Log.d(TAG, "Waiting for CVOX result with ID " + resultId + "..."); } while (true) { while (true) { // Fail if we received a callback from the future. // Fail if we received a callback from the future. if (mResultId > resultId) { if (mResultId > resultId) { if (DEBUG) if (DEBUG) { Log.w(TAG, "Aborted CVOX result"); Log.w(TAG, "Aborted CVOX result"); } return false; return false; } } final long elapsedTimeMillis = (SystemClock.uptimeMillis() - startTimeMillis); final long elapsedTimeMillis = (SystemClock.uptimeMillis() - startTimeMillis); // Succeed if we received the callback we were expecting. // Succeed if we received the callback we were expecting. if (DEBUG) if (DEBUG) { Log.w(TAG, "Check " + mResultId + " versus expected " + resultId); Log.w(TAG, "Check " + mResultId + " versus expected " + resultId); } if (mResultId == resultId) { if (mResultId == resultId) { if (DEBUG) if (DEBUG) { Log.w(TAG, "Received CVOX result after " + elapsedTimeMillis + " ms"); Log.w(TAG, "Received CVOX result after " + elapsedTimeMillis + " ms"); } return true; return true; } } Loading @@ -852,21 +892,24 @@ class AccessibilityInjector { // Fail if we've already exceeded the timeout. // Fail if we've already exceeded the timeout. if (waitTimeMillis <= 0) { if (waitTimeMillis <= 0) { if (DEBUG) if (DEBUG) { Log.w(TAG, "Timed out while waiting for CVOX result"); Log.w(TAG, "Timed out while waiting for CVOX result"); } return false; return false; } } try { try { if (DEBUG) if (DEBUG) { Log.w(TAG, "Start waiting..."); Log.w(TAG, "Start waiting..."); } mResultLock.wait(waitTimeMillis); mResultLock.wait(waitTimeMillis); } catch (InterruptedException ie) { } catch (InterruptedException ie) { if (DEBUG) if (DEBUG) { Log.w(TAG, "Interrupted while waiting for CVOX result"); Log.w(TAG, "Interrupted while waiting for CVOX result"); } } } } } } } /** /** * Callback exposed to JavaScript. Handles returning the result of a * Callback exposed to JavaScript. Handles returning the result of a Loading @@ -878,8 +921,9 @@ class AccessibilityInjector { @JavascriptInterface @JavascriptInterface @SuppressWarnings("unused") @SuppressWarnings("unused") public void onResult(String id, String result) { public void onResult(String id, String result) { if (DEBUG) if (DEBUG) { Log.w(TAG, "Saw CVOX result of '" + result + "' for ID " + id); Log.w(TAG, "Saw CVOX result of '" + result + "' for ID " + id); } final int resultId; final int resultId; try { try { Loading @@ -893,11 +937,34 @@ class AccessibilityInjector { mResult = Boolean.parseBoolean(result); mResult = Boolean.parseBoolean(result); mResultId = resultId; mResultId = resultId; } else { } else { if (DEBUG) if (DEBUG) { Log.w(TAG, "Result with ID " + resultId + " was stale vesus " + mResultId); Log.w(TAG, "Result with ID " + resultId + " was stale vesus " + mResultId); } } } mResultLock.notifyAll(); mResultLock.notifyAll(); } } } } /** * Requests a callback to ensure that the JavaScript interface for this * object has been added successfully. * * @param webView The web view to request a callback from. * @param callbackRunnable Runnable to execute if a callback is received. */ public void requestCallback(WebView webView, Runnable callbackRunnable) { mCallbackRunnable = callbackRunnable; webView.loadUrl("javascript:(function() { " + mInterfaceName + ".callback(); })();"); } @JavascriptInterface @SuppressWarnings("unused") public void callback() { if (mCallbackRunnable != null) { mMainHandler.post(mCallbackRunnable); mCallbackRunnable = null; } } } } } }
core/java/android/webkit/WebViewClassic.java +3 −0 Original line number Original line Diff line number Diff line Loading @@ -2500,6 +2500,9 @@ public final class WebViewClassic implements WebViewProvider, WebViewProvider.Sc // Remove all pending messages because we are restoring previous // Remove all pending messages because we are restoring previous // state. // state. mWebViewCore.removeMessages(); mWebViewCore.removeMessages(); if (isAccessibilityInjectionEnabled()) { getAccessibilityInjector().addAccessibilityApisIfNecessary(); } // Send a restore state message. // Send a restore state message. mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index); mWebViewCore.sendMessage(EventHub.RESTORE_STATE, index); } } Loading