diff options
Diffstat (limited to 'StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src')
9 files changed, 1667 insertions, 0 deletions
diff --git a/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/AbstractMobileAccessibilityHelper.java b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/AbstractMobileAccessibilityHelper.java new file mode 100644 index 00000000..d76a951f --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/AbstractMobileAccessibilityHelper.java @@ -0,0 +1,41 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +package com.phonegap.plugin.mobileaccessibility; + +import android.view.ViewParent; + +abstract class AbstractMobileAccessibilityHelper { + MobileAccessibility mMobileAccessibility; + ViewParent mParent; + public abstract void initialize(MobileAccessibility mobileAccessibility); + public abstract boolean isClosedCaptioningEnabled(); + public abstract boolean isScreenReaderRunning(); + public abstract boolean isTouchExplorationEnabled(); + public abstract void onAccessibilityStateChanged(boolean enabled); + public abstract void onCaptioningEnabledChanged(boolean enabled); + public abstract void onTouchExplorationStateChanged(boolean enabled); + public abstract void addStateChangeListeners(); + public abstract void removeStateChangeListeners(); + public abstract void announceForAccessibility(CharSequence text); + public abstract double getTextZoom(); + public abstract void setTextZoom(double textZoom); +} diff --git a/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/DonutMobileAccessibilityHelper.java b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/DonutMobileAccessibilityHelper.java new file mode 100644 index 00000000..e6972719 --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/DonutMobileAccessibilityHelper.java @@ -0,0 +1,187 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +package com.phonegap.plugin.mobileaccessibility; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; +import android.view.View; +import android.webkit.WebSettings; +import android.webkit.WebView; + +import java.lang.IllegalAccessException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +@TargetApi(Build.VERSION_CODES.DONUT) +public class DonutMobileAccessibilityHelper extends + AbstractMobileAccessibilityHelper { + AccessibilityManager mAccessibilityManager; + View mView; + + @Override + public void initialize(MobileAccessibility mobileAccessibility) { + mMobileAccessibility = mobileAccessibility; + WebView view; + try { + view = (WebView) mobileAccessibility.webView; + mView = view; + } catch(ClassCastException ce) { // cordova-android 4.0+ + try { + Method getView = mobileAccessibility.webView.getClass().getMethod("getView"); + mView = (View) getView.invoke(mobileAccessibility.webView); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + mAccessibilityManager = (AccessibilityManager) mMobileAccessibility.cordova.getActivity().getSystemService(Context.ACCESSIBILITY_SERVICE); + } + + @Override + public boolean isClosedCaptioningEnabled() { + return false; + } + + @Override + public boolean isScreenReaderRunning() { + return mAccessibilityManager.isEnabled(); + } + + @Override + public boolean isTouchExplorationEnabled() { + return false; + } + + @Override + public void onAccessibilityStateChanged(boolean enabled) { + mMobileAccessibility.onAccessibilityStateChanged(enabled); + } + + @Override + public void onCaptioningEnabledChanged(boolean enabled) { + mMobileAccessibility.onCaptioningEnabledChanged(enabled); + } + + @Override + public void onTouchExplorationStateChanged(boolean enabled) { + mMobileAccessibility.onTouchExplorationStateChanged(enabled); + } + + @Override + public void addStateChangeListeners() { + } + + @Override + public void removeStateChangeListeners() { + } + + @Override + public void announceForAccessibility(CharSequence text) { + if (!mAccessibilityManager.isEnabled()) { + return; + } + + final int eventType = AccessibilityEvent.TYPE_VIEW_FOCUSED; + final AccessibilityEvent event = AccessibilityEvent.obtain(eventType); + event.getText().add(text); + event.setEnabled(mView.isEnabled()); + event.setClassName(mView.getClass().getName()); + event.setPackageName(mView.getContext().getPackageName()); + event.setContentDescription(null); + + mAccessibilityManager.sendAccessibilityEvent(event); + } + + @SuppressWarnings("deprecation") + @Override + public double getTextZoom() { + double zoom = 100; + WebSettings.TextSize wTextSize = WebSettings.TextSize.NORMAL; + + try { + Method getSettings = mView.getClass().getMethod("getSettings"); + Object wSettings = getSettings.invoke(mView); + Method getTextSize = wSettings.getClass().getMethod("getTextSize"); + wTextSize = (WebSettings.TextSize) getTextSize.invoke(wSettings); + } catch(ClassCastException ce) { + ce.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + + switch (wTextSize) { + case LARGEST: + zoom = 200; + break; + case LARGER: + zoom = 150; + break; + case SMALLER: + zoom = 75; + break; + case SMALLEST: + zoom = 50; + break; + } + + return zoom; + } + + @SuppressWarnings("deprecation") + @Override + public void setTextZoom(double textZoom) { + WebSettings.TextSize wTextSize = WebSettings.TextSize.SMALLEST; + if (textZoom > 115) { + wTextSize = WebSettings.TextSize.LARGEST; + } else if (textZoom > 100) { + wTextSize = WebSettings.TextSize.LARGER; + } else if (textZoom == 100) { + wTextSize = WebSettings.TextSize.NORMAL; + } else if (textZoom > 50) { + wTextSize = WebSettings.TextSize.SMALLER; + } + //Log.i("MobileAccessibility", "fontScale = " + zoom + ", WebSettings.TextSize = " + wTextSize.toString()); + try { + Method getSettings = mView.getClass().getMethod("getSettings"); + Object wSettings = getSettings.invoke(mView); + Method setTextSize = wSettings.getClass().getMethod("setTextSize", WebSettings.TextSize.class); + setTextSize.invoke(wSettings, wTextSize); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } +} diff --git a/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/IceCreamSandwichMobileAccessibilityHelper.java b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/IceCreamSandwichMobileAccessibilityHelper.java new file mode 100644 index 00000000..f0cd252e --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/IceCreamSandwichMobileAccessibilityHelper.java @@ -0,0 +1,104 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +package com.phonegap.plugin.mobileaccessibility; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.annotation.TargetApi; +import android.os.Build; +import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; + +import java.lang.IllegalAccessException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) +public class IceCreamSandwichMobileAccessibilityHelper extends + DonutMobileAccessibilityHelper { + private AccessibilityStateChangeListener mAccessibilityStateChangeListener; + + @Override + public boolean isScreenReaderRunning() { + return mAccessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN).size() > 0; + } + + @Override + public void addStateChangeListeners() { + if (mAccessibilityStateChangeListener == null) { + mAccessibilityStateChangeListener = new InternalAccessibilityStateChangeListener(); + } + mAccessibilityManager.addAccessibilityStateChangeListener(mAccessibilityStateChangeListener); + } + + @Override + public void removeStateChangeListeners() { + mAccessibilityManager.removeAccessibilityStateChangeListener(mAccessibilityStateChangeListener); + mAccessibilityStateChangeListener = null; + } + + @Override + public double getTextZoom() { + double zoom = 100; + try { + Method getSettings = mView.getClass().getMethod("getSettings"); + Object wSettings = getSettings.invoke(mView); + Method getTextZoom = wSettings.getClass().getMethod("getTextZoom"); + zoom = Double.valueOf(getTextZoom.invoke(wSettings).toString()); + } catch (ClassCastException ce) { + ce.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + return zoom; + } + + @Override + public void setTextZoom(double textZoom) { + //Log.i("MobileAccessibility", "setTextZoom(" + zoom + ")"); + try { + Method getSettings = mView.getClass().getMethod("getSettings"); + Object wSettings = getSettings.invoke(mView); + Method setTextZoom = wSettings.getClass().getMethod("setTextZoom", Integer.TYPE); + setTextZoom.invoke(wSettings, (int) textZoom); + } catch (ClassCastException ce) { + ce.printStackTrace(); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + private class InternalAccessibilityStateChangeListener + implements AccessibilityStateChangeListener { + + @Override + public void onAccessibilityStateChanged(boolean enabled) { + mMobileAccessibility.onAccessibilityStateChanged(enabled); + } + } +} diff --git a/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/JellyBeanMobileAccessibilityHelper.java b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/JellyBeanMobileAccessibilityHelper.java new file mode 100644 index 00000000..7c97d247 --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/JellyBeanMobileAccessibilityHelper.java @@ -0,0 +1,52 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +package com.phonegap.plugin.mobileaccessibility; + +import com.phonegap.plugin.mobileaccessibility.MobileAccessibility; + +import android.annotation.TargetApi; +import android.os.Build; +import android.view.accessibility.AccessibilityEvent; + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN) +public class JellyBeanMobileAccessibilityHelper extends + IceCreamSandwichMobileAccessibilityHelper { + + @Override + public void initialize(MobileAccessibility mobileAccessibility) { + super.initialize(mobileAccessibility); + mParent = mView.getParentForAccessibility(); + } + + @Override + public void announceForAccessibility(CharSequence text) { + if (mAccessibilityManager.isEnabled() && mParent != null) { + mAccessibilityManager.interrupt(); + AccessibilityEvent event = AccessibilityEvent.obtain( + AccessibilityEvent.TYPE_ANNOUNCEMENT); + mView.onInitializeAccessibilityEvent(event); + event.getText().add(text); + event.setContentDescription(null); + mParent.requestSendAccessibilityEvent(mView, event); + } + } +} diff --git a/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/KitKatMobileAccessibilityHelper.java b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/KitKatMobileAccessibilityHelper.java new file mode 100644 index 00000000..11d342cb --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/KitKatMobileAccessibilityHelper.java @@ -0,0 +1,99 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +package com.phonegap.plugin.mobileaccessibility; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.annotation.TargetApi; +import android.content.Context; +import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener; +import android.view.accessibility.CaptioningManager; +import android.view.accessibility.CaptioningManager.CaptioningChangeListener; + +@TargetApi(19) +public class KitKatMobileAccessibilityHelper extends + JellyBeanMobileAccessibilityHelper { + private CaptioningManager mCaptioningManager; + private CaptioningChangeListener mCaptioningChangeListener; + private TouchExplorationStateChangeListener mTouchExplorationStateChangeListener; + + @Override + public void initialize(MobileAccessibility mobileAccessibility) { + super.initialize(mobileAccessibility); + mCaptioningManager = (CaptioningManager) mobileAccessibility.cordova.getActivity().getSystemService(Context.CAPTIONING_SERVICE); + } + + @Override + public boolean isScreenReaderRunning() { + return mAccessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_BRAILLE | AccessibilityServiceInfo.FEEDBACK_SPOKEN).size() > 0; + } + + @Override + public boolean isClosedCaptioningEnabled() { + return mCaptioningManager.isEnabled(); + } + + @Override + public boolean isTouchExplorationEnabled() { + return mAccessibilityManager.isTouchExplorationEnabled(); + } + + @Override + public void addStateChangeListeners() { + super.addStateChangeListeners(); + if (mCaptioningChangeListener == null) { + mCaptioningChangeListener = new CaptioningChangeListener() { + @Override + public void onEnabledChanged(boolean enabled) { + onCaptioningEnabledChanged(enabled); + } + }; + } + mCaptioningManager.addCaptioningChangeListener(mCaptioningChangeListener); + + if (mTouchExplorationStateChangeListener == null) { + mTouchExplorationStateChangeListener = new InternalTouchExplorationStateChangeListener(); + } + mAccessibilityManager.addTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); + } + + @Override + public void removeStateChangeListeners() { + super.removeStateChangeListeners(); + if (mCaptioningChangeListener != null) { + mCaptioningManager.removeCaptioningChangeListener(mCaptioningChangeListener); + mCaptioningChangeListener = null; + } + if (mTouchExplorationStateChangeListener != null) { + mAccessibilityManager.removeTouchExplorationStateChangeListener(mTouchExplorationStateChangeListener); + mTouchExplorationStateChangeListener = null; + } + } + + private class InternalTouchExplorationStateChangeListener + implements TouchExplorationStateChangeListener { + + @Override + public void onTouchExplorationStateChanged(boolean enabled) { + mMobileAccessibility.onTouchExplorationStateChanged(enabled); + } + } +} diff --git a/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/MobileAccessibility.java b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/MobileAccessibility.java new file mode 100644 index 00000000..a979420e --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/android/com/phonegap/plugin/mobileaccessibility/MobileAccessibility.java @@ -0,0 +1,330 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +package com.phonegap.plugin.mobileaccessibility; + +import org.apache.cordova.CallbackContext; +import org.apache.cordova.CordovaInterface; +import org.apache.cordova.CordovaPlugin; +import org.apache.cordova.CordovaWebView; +import org.apache.cordova.PluginResult; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.os.Build; +import android.webkit.WebView; + +import java.lang.IllegalAccessException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * This class provides information on the status of native accessibility services to JavaScript. + */ +public class MobileAccessibility extends CordovaPlugin { + private AbstractMobileAccessibilityHelper mMobileAccessibilityHelper; + private CallbackContext mCallbackContext = null; + private boolean mIsScreenReaderRunning = false; + private boolean mClosedCaptioningEnabled = false; + private boolean mTouchExplorationEnabled = false; + private boolean mCachedIsScreenReaderRunning = false; + private float mFontScale = 1; + + @Override + public void initialize(CordovaInterface cordova, CordovaWebView webView) { + super.initialize(cordova, webView); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + mMobileAccessibilityHelper = new KitKatMobileAccessibilityHelper(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + mMobileAccessibilityHelper = new JellyBeanMobileAccessibilityHelper(); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + mMobileAccessibilityHelper = new IceCreamSandwichMobileAccessibilityHelper(); + } else { + mMobileAccessibilityHelper = new DonutMobileAccessibilityHelper(); + } + mMobileAccessibilityHelper.initialize(this); + } + + @Override + public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException { + try { + if (action.equals("isScreenReaderRunning")) { + isScreenReaderRunning(callbackContext); + return true; + } else if (action.equals("isClosedCaptioningEnabled")) { + isClosedCaptioningEnabled(callbackContext); + return true; + } else if (action.equals("isTouchExplorationEnabled")) { + isTouchExplorationEnabled(callbackContext); + return true; + } else if (action.equals("postNotification")) { + if (args.length() > 1) { + String string = args.getString(1); + if (!string.isEmpty()) { + announceForAccessibility(string, callbackContext); + } + } + return true; + } else if(action.equals("getTextZoom")) { + getTextZoom(callbackContext); + return true; + } else if(action.equals("setTextZoom")) { + if (args.length() > 0) { + double textZoom = args.getDouble(0); + if (textZoom > 0) { + setTextZoom(textZoom, callbackContext); + } + } + return true; + } else if(action.equals("updateTextZoom")) { + updateTextZoom(callbackContext); + return true; + } else if (action.equals("start")) { + start(callbackContext); + return true; + } else if (action.equals("stop")) { + stop(); + return true; + } + } catch (JSONException e) { + e.printStackTrace(); + callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.JSON_EXCEPTION)); + } + return false; + } + + /** + * Called when the system is about to pause the current activity + * + * @param multitasking Flag indicating if multitasking is turned on for app + */ + @Override + public void onPause(boolean multitasking) { + //Log.i("MobileAccessibility", "onPause"); + mCachedIsScreenReaderRunning = mIsScreenReaderRunning; + } + + /** + * Called when the activity will start interacting with the user. + * + * @param multitasking Flag indicating if multitasking is turned on for app + */ + @Override + public void onResume(boolean multitasking) { + //Log.i("MobileAccessibility", "onResume"); + if (mIsScreenReaderRunning && !mCachedIsScreenReaderRunning) { + //Log.i("MobileAccessibility", "Reloading page on reload because the Accessibility State has changed."); + mCachedIsScreenReaderRunning = mIsScreenReaderRunning; + stop(); + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + WebView view; + try { + view = (WebView) webView; + view.reload(); + } catch(ClassCastException ce) { // cordova-android 4.0+ + try { // cordova-android 4.0+ + Method getView = webView.getClass().getMethod("getView"); + Method reload = getView.invoke(webView).getClass().getMethod("reload"); + reload.invoke(webView); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + }); + } + } + + /** + * The final call you receive before your activity is destroyed. + */ + public void onDestroy() { + stop(); + } + + private void isScreenReaderRunning(final CallbackContext callbackContext) { + mIsScreenReaderRunning = mMobileAccessibilityHelper.isScreenReaderRunning(); + cordova.getThreadPool().execute(new Runnable() { + public void run() { + callbackContext.success(mIsScreenReaderRunning ? 1 : 0); + } + }); + } + + protected boolean isScreenReaderRunning() { + mIsScreenReaderRunning = mMobileAccessibilityHelper.isScreenReaderRunning(); + return mIsScreenReaderRunning; + } + + private void isClosedCaptioningEnabled(final CallbackContext callbackContext) { + mClosedCaptioningEnabled = mMobileAccessibilityHelper.isClosedCaptioningEnabled(); + cordova.getThreadPool().execute(new Runnable() { + public void run() { + callbackContext.success(mClosedCaptioningEnabled ? 1 : 0); + } + }); + } + + protected boolean isClosedCaptioningEnabled() { + mClosedCaptioningEnabled = mMobileAccessibilityHelper.isClosedCaptioningEnabled(); + return mClosedCaptioningEnabled; + } + + private void isTouchExplorationEnabled(final CallbackContext callbackContext) { + mTouchExplorationEnabled= mMobileAccessibilityHelper.isTouchExplorationEnabled(); + cordova.getThreadPool().execute(new Runnable() { + public void run() { + callbackContext.success(mTouchExplorationEnabled ? 1 : 0); + } + }); + } + + protected boolean isTouchExplorationEnabled() { + mTouchExplorationEnabled = mMobileAccessibilityHelper.isTouchExplorationEnabled(); + return mTouchExplorationEnabled; + } + + private void announceForAccessibility(CharSequence text, final CallbackContext callbackContext) { + mMobileAccessibilityHelper.announceForAccessibility(text); + if (callbackContext != null) { + JSONObject info = new JSONObject(); + try { + info.put("stringValue", text); + info.put("wasSuccessful", mIsScreenReaderRunning); + } catch (JSONException e) { + e.printStackTrace(); + } + callbackContext.success(info); + } + } + + public void onAccessibilityStateChanged(boolean enabled) { + mIsScreenReaderRunning = enabled; + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + sendMobileAccessibilityStatusChangedCallback(); + } + }); + } + + public void onCaptioningEnabledChanged(boolean enabled) { + mClosedCaptioningEnabled = enabled; + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + sendMobileAccessibilityStatusChangedCallback(); + } + }); + } + + public void onTouchExplorationStateChanged(boolean enabled) { + mTouchExplorationEnabled = enabled; + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + sendMobileAccessibilityStatusChangedCallback(); + } + }); + } + + private void getTextZoom(final CallbackContext callbackContext) { + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + final double textZoom = mMobileAccessibilityHelper.getTextZoom(); + if (callbackContext != null) { + callbackContext.success((int) textZoom); + } + } + }); + } + + private void setTextZoom(final double textZoom, final CallbackContext callbackContext) { + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + mMobileAccessibilityHelper.setTextZoom(textZoom); + if (callbackContext != null) { + callbackContext.success((int) mMobileAccessibilityHelper.getTextZoom()); + } + } + }); + } + + public void setTextZoom(final double textZoom) { + cordova.getActivity().runOnUiThread(new Runnable() { + public void run() { + mMobileAccessibilityHelper.setTextZoom(textZoom); + } + }); + } + + private void updateTextZoom(final CallbackContext callbackContext) { + float fontScale = cordova.getActivity().getResources().getConfiguration().fontScale; + if (fontScale != mFontScale) { + mFontScale = fontScale; + } + final double textZoom = Math.round(mFontScale * 100); + setTextZoom(textZoom, callbackContext); + } + + private void sendMobileAccessibilityStatusChangedCallback() { + if (this.mCallbackContext != null) { + PluginResult result = new PluginResult(PluginResult.Status.OK, getMobileAccessibilityStatus()); + result.setKeepCallback(true); + this.mCallbackContext.sendPluginResult(result); + } + } + + /* Get the current mobile accessibility status. */ + private JSONObject getMobileAccessibilityStatus() { + JSONObject status = new JSONObject(); + try { + status.put("isScreenReaderRunning", mIsScreenReaderRunning); + status.put("isClosedCaptioningEnabled", mClosedCaptioningEnabled); + status.put("isTouchExplorationEnabled", mTouchExplorationEnabled); + //Log.i("MobileAccessibility", "MobileAccessibility.isScreenReaderRunning == " + status.getString("isScreenReaderRunning") + + // "\nMobileAccessibility.isClosedCaptioningEnabled == " + status.getString("isClosedCaptioningEnabled") + + // "\nMobileAccessibility.isTouchExplorationEnabled == " + status.getString("isTouchExplorationEnabled") ); + } catch (JSONException e) { + e.printStackTrace(); + } + return status; + } + + private void start(CallbackContext callbackContext) { + //Log.i("MobileAccessibility", "MobileAccessibility.start"); + mCallbackContext = callbackContext; + mMobileAccessibilityHelper.addStateChangeListeners(); + sendMobileAccessibilityStatusChangedCallback(); + } + + private void stop() { + //Log.i("MobileAccessibility", "MobileAccessibility.stop"); + if (mCallbackContext != null) { + sendMobileAccessibilityStatusChangedCallback(); + mMobileAccessibilityHelper.removeStateChangeListeners(); + mCallbackContext = null; + } + } +} diff --git a/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/ios/CDVMobileAccessibility.h b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/ios/CDVMobileAccessibility.h new file mode 100644 index 00000000..662abbea --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/ios/CDVMobileAccessibility.h @@ -0,0 +1,82 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +#import <Cordova/CDVPlugin.h> + +static const int BASE_UI_FONT_TEXT_STYLE_BODY_POINT_SIZE = 16; + +@interface CDVMobileAccessibility : CDVPlugin { + NSString* callbackId; + NSString* commandCallbackId; + BOOL boldTextEnabled; + BOOL closedCaptioningEnabled; + BOOL darkerSystemColorsEnabled; + BOOL grayscaleEnabled; + BOOL guidedAccessEnabled; + BOOL invertColorsEnabled; + BOOL monoAudioEnabled; + BOOL reduceMotionEnabled; + BOOL reduceTransparencyEnabled; + BOOL speakScreenEnabled; + BOOL speakSelectionEnabled; + BOOL switchControlRunning; + BOOL voiceOverRunning; +} + +@property (strong) NSString* callbackId; +@property (strong) NSString* commandCallbackId; +@property BOOL boldTextEnabled; +@property BOOL closedCaptioningEnabled; +@property BOOL darkerSystemColorsEnabled; +@property BOOL grayscaleEnabled; +@property BOOL guidedAccessEnabled; +@property BOOL invertColorsEnabled; +@property BOOL monoAudioEnabled; +@property BOOL reduceMotionEnabled; +@property BOOL reduceTransparencyEnabled; +@property BOOL speakScreenEnabled; +@property BOOL speakSelectionEnabled; +@property BOOL switchControlRunning; +@property BOOL voiceOverRunning; +@property double mFontScale; + + +- (void) isBoldTextEnabled:(CDVInvokedUrlCommand*)command; +- (void) isClosedCaptioningEnabled:(CDVInvokedUrlCommand*)command; +- (void) isDarkerSystemColorsEnabled:(CDVInvokedUrlCommand*)command; +- (void) isGrayscaleEnabled:(CDVInvokedUrlCommand*)command; +- (void) isGuidedAccessEnabled:(CDVInvokedUrlCommand*)command; +- (void) isInvertColorsEnabled:(CDVInvokedUrlCommand*)command; +- (void) isMonoAudioEnabled:(CDVInvokedUrlCommand*)command; +- (void) isReduceMotionEnabled:(CDVInvokedUrlCommand*)command; +- (void) isReduceTransparencyEnabled:(CDVInvokedUrlCommand*)command; +- (void) isScreenReaderRunning:(CDVInvokedUrlCommand*)command; +- (void) isSpeakScreenEnabled:(CDVInvokedUrlCommand*)command; +- (void) isSpeakSelectionEnabled:(CDVInvokedUrlCommand*)command; +- (void) isSwitchControlRunning:(CDVInvokedUrlCommand*)command; +- (void) getTextZoom:(CDVInvokedUrlCommand*)command; +- (void) setTextZoom:(CDVInvokedUrlCommand*)command; +- (void) updateTextZoom:(CDVInvokedUrlCommand*)command; +- (void) postNotification:(CDVInvokedUrlCommand*)command; +- (void) start:(CDVInvokedUrlCommand*)command; +- (void) stop:(CDVInvokedUrlCommand*)command; + +@end
\ No newline at end of file diff --git a/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/ios/CDVMobileAccessibility.m b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/ios/CDVMobileAccessibility.m new file mode 100644 index 00000000..ad8da029 --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/ios/CDVMobileAccessibility.m @@ -0,0 +1,511 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +#import "CDVMobileAccessibility.h" +#import <Cordova/CDVAvailability.h> +#import <MediaAccessibility/MediaAccessibility.h> + +@interface CDVMobileAccessibility () + // add any property overrides + -(double) mGetTextZoom; + -(void) mSetTextZoom:(double)zoom; +@end + +@implementation CDVMobileAccessibility + +@synthesize callbackId; +@synthesize commandCallbackId; +@synthesize boldTextEnabled; +@synthesize closedCaptioningEnabled; +@synthesize darkerSystemColorsEnabled; +@synthesize grayscaleEnabled; +@synthesize guidedAccessEnabled; +@synthesize invertColorsEnabled; +@synthesize monoAudioEnabled; +@synthesize reduceMotionEnabled; +@synthesize reduceTransparencyEnabled; +@synthesize speakScreenEnabled; +@synthesize speakSelectionEnabled; +@synthesize switchControlRunning; +@synthesize voiceOverRunning; +@synthesize mFontScale; + +#define iOS7Delta (([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0 ) ? 20 : 0 ) +#define iOS8Delta (([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0 ) ? 30 : 0 ) + +// ////////////////////////////////////////////////// + +- (id)settingForKey:(NSString*)key +{ + return [self.commandDelegate.settings objectForKey:[key lowercaseString]]; +} + +- (void)pluginInitialize +{ + // SETTINGS //////////////////////// + + NSString* setting = nil; + + setting = @"YourConfigXmlSettingHere"; + if ([self settingForKey:setting]) { + // set your setting, other init here + } + + mFontScale = 1; + + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPause) name:UIApplicationDidEnterBackgroundNotification object:nil]; + +} + +// ////////////////////////////////////////////////// + +- (void)dealloc +{ + [self stop:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; +} + +- (void)onReset +{ + [self stop:nil]; +} + +- (void) onPause { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onResume) name:UIApplicationWillEnterForegroundNotification object:nil]; +} + +- (void)onResume +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPause) name:UIApplicationDidEnterBackgroundNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onApplicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; +} + +- (void)onApplicationDidBecomeActive +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil]; + [self performSelector:@selector(sendMobileAccessibilityStatusChangedCallback) withObject:nil afterDelay:0.1 ]; +} + +// ////////////////////////////////////////////////// + +#pragma Plugin interface + +- (void)isBoldTextEnabled:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + if (iOS8Delta) { + self.boldTextEnabled = UIAccessibilityIsBoldTextEnabled(); + } else { + self.boldTextEnabled = false; + } + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.boldTextEnabled]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)isClosedCaptioningEnabled:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + self.closedCaptioningEnabled = [self getClosedCaptioningEnabledStatus]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.closedCaptioningEnabled]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)isDarkerSystemColorsEnabled:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + if (iOS8Delta) { + self.darkerSystemColorsEnabled = UIAccessibilityDarkerSystemColorsEnabled(); + } else { + self.darkerSystemColorsEnabled = false; + } + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.darkerSystemColorsEnabled]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)isGrayscaleEnabled:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + if (iOS8Delta) { + self.grayscaleEnabled = UIAccessibilityIsGrayscaleEnabled(); + } else { + self.grayscaleEnabled = false; + } + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.grayscaleEnabled]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)isGuidedAccessEnabled:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + self.guidedAccessEnabled = UIAccessibilityIsGuidedAccessEnabled(); + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.guidedAccessEnabled]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)isInvertColorsEnabled:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + self.invertColorsEnabled = UIAccessibilityIsInvertColorsEnabled(); + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.invertColorsEnabled]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)isMonoAudioEnabled:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + self.monoAudioEnabled = UIAccessibilityIsMonoAudioEnabled(); + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.monoAudioEnabled]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)isReduceMotionEnabled:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + if (iOS8Delta) { + self.reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled(); + } else { + self.reduceMotionEnabled = false; + } + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.reduceMotionEnabled]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)isReduceTransparencyEnabled:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + if (iOS8Delta) { + self.reduceTransparencyEnabled = UIAccessibilityIsReduceTransparencyEnabled(); + } else { + self.reduceTransparencyEnabled = false; + } + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.reduceTransparencyEnabled]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)isScreenReaderRunning:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + self.voiceOverRunning = UIAccessibilityIsVoiceOverRunning(); + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.voiceOverRunning]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)isSpeakScreenEnabled:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + if (iOS8Delta) { + self.speakScreenEnabled = UIAccessibilityIsSpeakScreenEnabled(); + } else { + self.speakScreenEnabled = false; + } + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.speakScreenEnabled]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)isSpeakSelectionEnabled:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + if (iOS8Delta) { + self.speakSelectionEnabled = UIAccessibilityIsSpeakSelectionEnabled(); + } else { + self.speakSelectionEnabled = false; + } + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.speakSelectionEnabled]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +- (void)isSwitchControlRunning:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + if (iOS8Delta) { + self.switchControlRunning = UIAccessibilityIsSwitchControlRunning(); + } else { + self.switchControlRunning = false; + } + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.switchControlRunning]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +-(double) mGetFontScale +{ + double fontScale = 1; + if (iOS7Delta) { + fontScale = [[UIFont preferredFontForTextStyle:UIFontTextStyleBody] pointSize] / BASE_UI_FONT_TEXT_STYLE_BODY_POINT_SIZE; + } + return fontScale; +} + +-(double) mGetTextZoom +{ + double zoom = round(mFontScale * 100); + // NSLog(@"mGetTextZoom %f%%", zoom); + return zoom; +} + +- (void) getTextZoom:(CDVInvokedUrlCommand *)command +{ + double zoom = [self mGetTextZoom]; + [self.commandDelegate runInBackground:^{ + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble: zoom]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; +} + +-(void) mSetTextZoom:(double)zoom +{ + // NSLog(@"mSetTextZoom %f%%'", zoom); + mFontScale = zoom/100; + if (iOS7Delta) { + NSString *jsString = [[NSString alloc] initWithFormat:@"document.getElementsByTagName('body')[0].style.webkitTextSizeAdjust= '%f%%'", zoom]; + [self.commandDelegate evalJs:jsString]; + } +} + +- (void) setTextZoom:(CDVInvokedUrlCommand *)command +{ + if (command != nil && [command.arguments count] > 0) { + double zoom = [[command.arguments objectAtIndex:0] doubleValue]; + [self mSetTextZoom:zoom]; + + [self.commandDelegate runInBackground:^{ + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:zoom]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; + } +} + +- (void)updateTextZoom:(CDVInvokedUrlCommand *)command +{ + float fontScale = [self mGetFontScale]; + if (fontScale != mFontScale) { + mFontScale = fontScale; + } + double zoom = [self mGetTextZoom]; + // NSLog(@"updateTextZoom %d%%'", zoom); + [self mSetTextZoom:zoom]; + + if (command != nil && command.callbackId != nil) { + [self.commandDelegate runInBackground:^{ + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDouble:zoom]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + }]; + } +} + +- (void)postNotification:(CDVInvokedUrlCommand *)command +{ + CDVPluginResult* result = nil; + uint32_t notificationType = [[command.arguments objectAtIndex:0] intValue]; + NSString* notificationString = [command.arguments count] > 1 ? [command.arguments objectAtIndex:1] : @""; + + if (notificationString == nil) { + notificationString = @""; + } + if (UIAccessibilityIsVoiceOverRunning() && + [self isValidNotificationType:notificationType]) { + [self.commandDelegate runInBackground:^{ + if (notificationType == UIAccessibilityAnnouncementNotification) { + self.commandCallbackId = command.callbackId; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityAnnouncementDidFinish:) name:UIAccessibilityAnnouncementDidFinishNotification object:nil]; + } + + UIAccessibilityPostNotification(notificationType, notificationString); + + if (notificationType != UIAccessibilityAnnouncementNotification) { + NSMutableDictionary* data = [NSMutableDictionary dictionaryWithCapacity:2]; + [data setObject:notificationString forKey:@"stringValue"]; + [data setObject:@"true" forKey:@"wasSuccessful"]; + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:data]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } + }]; + } else { + result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR]; + [self.commandDelegate sendPluginResult:result callbackId:command.callbackId]; + } +} + +- (BOOL)isValidNotificationType:(uint32_t)notificationType +{ + return (notificationType == UIAccessibilityScreenChangedNotification + || notificationType == UIAccessibilityLayoutChangedNotification + || notificationType == UIAccessibilityAnnouncementNotification + || notificationType == UIAccessibilityPageScrolledNotification); +} + +- (void)mobileAccessibilityAnnouncementDidFinish:(NSNotification *)dict +{ + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityAnnouncementDidFinishNotification object:nil]; + + NSString* valueSpoken = [[dict userInfo] objectForKey:UIAccessibilityAnnouncementKeyStringValue]; + NSString* wasSuccessful = [[dict userInfo] objectForKey:UIAccessibilityAnnouncementKeyWasSuccessful]; + + NSMutableDictionary* data = [NSMutableDictionary dictionaryWithCapacity:2]; + [data setObject:valueSpoken forKey:@"stringValue"]; + [data setObject:wasSuccessful forKey:@"wasSuccessful"]; + + if (self.commandCallbackId) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:data]; + [self.commandDelegate sendPluginResult:result callbackId:self.commandCallbackId]; + self.commandCallbackId = nil; + } +} + +- (BOOL)getClosedCaptioningEnabledStatus +{ + BOOL status = false; + if (&MACaptionAppearanceGetDisplayType) { + status = (MACaptionAppearanceGetDisplayType(kMACaptionAppearanceDomainUser) > kMACaptionAppearanceDisplayTypeAutomatic); + } else { + status = UIAccessibilityIsClosedCaptioningEnabled(); + } + return status; +} + +- (void)mobileAccessibilityStatusChanged:(NSNotification *)notification +{ + [self sendMobileAccessibilityStatusChangedCallback]; +} + +- (void)sendMobileAccessibilityStatusChangedCallback +{ + if (self.callbackId) { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getMobileAccessibilityStatus]]; + [result setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:result callbackId:self.callbackId]; + } +} + +/* Get the current mobile accessibility status. */ +- (NSDictionary*)getMobileAccessibilityStatus +{ + self.boldTextEnabled = UIAccessibilityIsBoldTextEnabled(); + self.closedCaptioningEnabled = [self getClosedCaptioningEnabledStatus]; + self.darkerSystemColorsEnabled = UIAccessibilityDarkerSystemColorsEnabled(); + self.grayscaleEnabled = UIAccessibilityIsGrayscaleEnabled(); + self.guidedAccessEnabled = UIAccessibilityIsGuidedAccessEnabled(); + self.invertColorsEnabled = UIAccessibilityIsInvertColorsEnabled(); + self.monoAudioEnabled = UIAccessibilityIsMonoAudioEnabled(); + self.reduceMotionEnabled = UIAccessibilityIsReduceMotionEnabled(); + self.reduceTransparencyEnabled = UIAccessibilityIsReduceTransparencyEnabled(); + self.speakScreenEnabled = UIAccessibilityIsSpeakScreenEnabled(); + self.speakSelectionEnabled = UIAccessibilityIsSpeakSelectionEnabled(); + self.switchControlRunning = UIAccessibilityIsSwitchControlRunning(); + self.voiceOverRunning = UIAccessibilityIsVoiceOverRunning(); + + NSMutableDictionary* mobileAccessibilityData = [NSMutableDictionary dictionaryWithCapacity:5]; + [mobileAccessibilityData setObject:[NSNumber numberWithBool:self.boldTextEnabled] forKey:@"isBoldTextEnabled"]; + [mobileAccessibilityData setObject:[NSNumber numberWithBool:self.closedCaptioningEnabled] forKey:@"isClosedCaptioningEnabled"]; + [mobileAccessibilityData setObject:[NSNumber numberWithBool:self.darkerSystemColorsEnabled] forKey:@"isDarkerSystemColorsEnabled"]; + [mobileAccessibilityData setObject:[NSNumber numberWithBool:self.grayscaleEnabled] forKey:@"isGrayscaleEnabled"]; + [mobileAccessibilityData setObject:[NSNumber numberWithBool:self.guidedAccessEnabled] forKey:@"isGuidedAccessEnabled"]; + [mobileAccessibilityData setObject:[NSNumber numberWithBool:self.invertColorsEnabled] forKey:@"isInvertColorsEnabled"]; + [mobileAccessibilityData setObject:[NSNumber numberWithBool:self.monoAudioEnabled] forKey:@"isMonoAudioEnabled"]; + [mobileAccessibilityData setObject:[NSNumber numberWithBool:self.reduceMotionEnabled] forKey:@"isReduceMotionEnabled"]; + [mobileAccessibilityData setObject:[NSNumber numberWithBool:self.reduceTransparencyEnabled] forKey:@"isReduceTransparencyEnabled"]; + [mobileAccessibilityData setObject:[NSNumber numberWithBool:self.speakScreenEnabled] forKey:@"isSpeakScreenEnabled"]; + [mobileAccessibilityData setObject:[NSNumber numberWithBool:self.speakSelectionEnabled] forKey:@"isSpeakSelectionEnabled"]; + [mobileAccessibilityData setObject:[NSNumber numberWithBool:self.switchControlRunning] forKey:@"isSwitchControlRunning"]; + [mobileAccessibilityData setObject:[NSNumber numberWithBool:self.voiceOverRunning] forKey:@"isScreenReaderRunning"]; + return mobileAccessibilityData; +} + + +/* start MobileAccessibility monitoring */ +- (void)start:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + self.callbackId = command.callbackId; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilityClosedCaptioningStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilityGuidedAccessStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilityInvertColorsStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilityMonoAudioStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilityVoiceOverStatusChanged object:nil]; + + if (iOS8Delta) { + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilityBoldTextStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilityDarkerSystemColorsStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilityGrayscaleStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilityReduceMotionStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilityReduceTransparencyStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilitySpeakScreenStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilitySpeakSelectionStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilitySwitchControlStatusDidChangeNotification object:nil]; + } + + // Update the callback on start + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getMobileAccessibilityStatus]]; + [result setKeepCallbackAsBool:YES]; + [self.commandDelegate sendPluginResult:result callbackId:self.callbackId]; + }]; +} + +/* stop MobileAccessibility monitoring */ +- (void)stop:(CDVInvokedUrlCommand*)command +{ + [self.commandDelegate runInBackground:^{ + // callback one last time to clear the callback function on JS side + if (self.callbackId) + { + CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:[self getMobileAccessibilityStatus]]; + [result setKeepCallbackAsBool:NO]; + [self.commandDelegate sendPluginResult:result callbackId:self.callbackId]; + } + self.callbackId = nil; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityClosedCaptioningStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityGuidedAccessStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityInvertColorsStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityAnnouncementDidFinishNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityMonoAudioStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityVoiceOverStatusChanged object:nil]; + + if (iOS8Delta) { + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityBoldTextStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityDarkerSystemColorsStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityGrayscaleStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityReduceMotionStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilityReduceTransparencyStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilitySpeakScreenStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilitySpeakSelectionStatusDidChangeNotification object:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self name:UIAccessibilitySwitchControlStatusDidChangeNotification object:nil]; + } + }]; +} + +@end diff --git a/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/windows/mobile-accessibility-proxy.js b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/windows/mobile-accessibility-proxy.js new file mode 100644 index 00000000..166c3e5e --- /dev/null +++ b/StoneIsland/plugins/phonegap-plugin-mobile-accessibility/src/windows/mobile-accessibility-proxy.js @@ -0,0 +1,261 @@ +/** + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * +*/ + +/*global Windows:true*/ + +var cordova = require('cordova'), + accessibilitySettings, + uiSettings, + callbackContext, + fontScale = 1, + styleElement, + bodyStyleDeclaration, + audio, + synth; + +function getAccessibilitySettings() { + if (!accessibilitySettings) { + accessibilitySettings = new Windows.UI.ViewManagement.AccessibilitySettings(); + } + return accessibilitySettings; +} + +function getUISettings() { + if (!uiSettings) { + uiSettings = new Windows.UI.ViewManagement.UISettings(); + } + return uiSettings; +} + +function getFontScale() { + if (!uiSettings) { + uiSettings = getUISettings(); + } + var scale = uiSettings.textScaleFactor; + // console.log("getFontScale " + scale); + return scale; +} + +function getTextZoom() { + var zoom = Math.round(fontScale * 100); + // console.log("getTextZoom " + zoom); + return zoom; +} + +function createStyleElement() { + if (!styleElement) { + styleElement = document.createElement("style"); + styleElement.type = "text/css"; + document.head.appendChild(styleElement); + } +} + +function setTextZoom(zoom) { + // console.log("setTextZoom " + zoom); + fontScale = zoom/100; + + createStyleElement(); + + if (bodyStyleDeclaration) { + styleElement.removeChild(bodyStyleDeclaration); + } + + bodyStyleDeclaration = document.createTextNode("body {-ms-text-size-adjust: " + zoom + "%}"); + + styleElement.appendChild(bodyStyleDeclaration); +} + +function onHighContrastChanged(event) { + cordova.fireWindowEvent(MobileAccessibilityNotifications.HIGH_CONTRAST_CHANGED, + getMobileAccessibilityStatus()); + sendMobileAccessibilityStatusChangedCallback(); +} + +function sendMobileAccessibilityStatusChangedCallback() { + console.log("sendMobileAccessibilityStatusChangedCallback"); + if (callbackContext && typeof callbackContext === "function") { + callbackContext(getMobileAccessibilityStatus()); + } +} + +function getMobileAccessibilityStatus() { + return { + isScreenReaderRunning: false, + isClosedCaptioningEnabled: false, + isHighContrastEnabled: accessibilitySettings.highContrast, + highContrastScheme: (accessibilitySettings.highContrast ? accessibilitySettings.highContrastScheme : undefined) + }; +} + +module.exports = { + start:function(win, fail, args) { + if (typeof win === "function") { + callbackContext = win; + } + if (!accessibilitySettings) { + accessibilitySettings = getAccessibilitySettings(); + } + accessibilitySettings.addEventListener("highcontrastchanged", onHighContrastChanged); + + if (!uiSettings) { + uiSettings = getUISettings(); + } + }, + stop:function(win, fail, args) { + if (accessibilitySettings) { + accessibilitySettings.removeEventListener("highcontrastchanged", onHighContrastChanged); + } + + if (synth) { + synth.close(); + synth = null; + } + + if (audio) { + audio = null; + } + + if (callbackContext) { + callbackContext = null; + } + }, + isScreenReaderRunning:function(win, fail, args) { + console && console.error && console.error("Error : Windows 8.1 does not yet have a way to detect that Narrator is running."); + fail && setTimeout(function() { + fail(new Error("Windows Phone 8.1 does not yet have a way to detect that Narrator is running.")); + }, 0); + if (win && typeof win === "function") { + win(false); + } + }, + isClosedCaptioningEnabled:function(win, fail, args) { + console && console.error && console.error("Error : MobileAccessibility.isClosedCaptioningEnabled is not yet implemented on Windows 8.1."); + fail && setTimeout(function() { + fail(new Error("MobileAccessibility.isClosedCaptioningEnabled is not yet implemented on Windows 8.1.")); + }, 0); + if (win && typeof win === "function") { + win(false); + } + }, + isHighContrastEnabled:function(win, fail, args) { + if (!accessibilitySettings) { + accessibilitySettings = getAccessibilitySettings(); + } + + if (win && typeof win === "function") { + win(accessibilitySettings.highContrast); + } + return accessibilitySettings.highContrast; + }, + getHighContrastScheme:function(win, fail, args) { + if (!accessibilitySettings) { + accessibilitySettings = getAccessibilitySettings(); + } + + var highContrastScheme = accessibilitySettings.highContrast ? accessibilitySettings.highContrastScheme : undefined; + + if (win) { + win(highContrastScheme); + } + return highContrastScheme; + }, + getTextZoom:function(win, fail, args) { + var zoom = Math.round(fontScale * 100); + + if (win) { + win(zoom); + } + + return zoom; + }, + setTextZoom:function(win, fail, args) { + var zoom = args[0]; + setTextZoom(args[0]); + + if (win) { + win(zoom); + } + + return zoom; + }, + updateTextZoom:function(win, fail, args) { + var scale = getFontScale(), + zoom; + if (scale !== fontScale) { + fontScale = scale; + } + zoom = getTextZoom(); + setTextZoom(zoom); + + if (win) { + win(zoom); + } + + return zoom; + }, + postNotification:function(win, fail, args) { + var str = args[1]; + + if (str && typeof str === "string") { + // The object for controlling and playing audio. + if (!audio) { + audio = new Audio(); + } else if (audio.pause) { + audio.pause(); + audio.src = null; + } + + // The object for controlling the speech synthesis engine (voice). + if (!synth) { + synth = new Windows.Media.SpeechSynthesis.SpeechSynthesizer(); + } + + if (str && str.length && str !== "\u200b") { + + // Generate the audio stream from plain text. + synth.synthesizeTextToStreamAsync(str).then(function (markersStream) { + + // Convert the stream to a URL Blob. + var blob = MSApp.createBlobFromRandomAccessStream(markersStream.contentType, markersStream); + // Send the Blob to the audio object. + audio.src = URL.createObjectURL(blob, { oneTimeOnly: true }); + // Start at beginning when speak is hit + markersStream.seek(0); + audio.AutoPlay = Boolean(true); + + // Audio on completed + audio.onended = function () { + if (win) { + win({ + stringValue: str, + wasSuccessful: true + }); + } + }; + + audio.play(); + }); + } + } + } +}; + +require("cordova/exec/proxy").add("MobileAccessibility", module.exports); |
