Detect and receive notification of more iOS accessibility features

Adds following methods for detecting iOS accessibility features:
MobileAccessibility.isGuidedAccessEnabled()
MobileAccessibility.isInvertColorsEnabled()
MobileAccessibility.isGuidedAccessEnabled()
MobileAccessibility.isInvertColorsEnabled()
MobileAccessibility.isMonoAudioEnabled()

Adds corresponding window events to subscribe to notifications of
accessibility feature status updates:
“guidedaccessstatusdidchange”,
"invertcolorsstatusdidchange”,
"monoaudiostatusdidchange"

Adds following method for posting a notification as a string to be
announced by VoiceOver:
MobileAccessibility.postAnnouncementNotification()
This commit is contained in:
Michael Jordan
2014-01-17 16:48:22 -05:00
parent 97dd76c7df
commit 2643674b7f
3 changed files with 262 additions and 37 deletions
+16
View File
@@ -21,12 +21,28 @@
@interface CDVMobileAccessibility : CDVPlugin {
NSString* callbackId;
NSString* commandCallbackId;
BOOL voiceOverRunning;
BOOL closeCaptioningEnabled;
BOOL guidedAccessEnabled;
BOOL invertColorsEnabled;
BOOL monoAudioEnabled;
}
@property (strong) NSString* callbackId;
@property (strong) NSString* commandCallbackId;
@property BOOL voiceOverRunning;
@property BOOL closeCaptioningEnabled;
@property BOOL guidedAccessEnabled;
@property BOOL invertColorsEnabled;
@property BOOL monoAudioEnabled;
- (void) isVoiceOverRunning:(CDVInvokedUrlCommand*)command;
- (void) isClosedCaptioningEnabled:(CDVInvokedUrlCommand*)command;
- (void) isGuidedAccessEnabled:(CDVInvokedUrlCommand*)command;
- (void) isInvertColorsEnabled:(CDVInvokedUrlCommand*)command;
- (void) isMonoAudioEnabled:(CDVInvokedUrlCommand*)command;
- (void) postAnnouncementNotification:(CDVInvokedUrlCommand*)command;
- (void) start:(CDVInvokedUrlCommand*)command;
- (void) stop:(CDVInvokedUrlCommand*)command;
+160 -25
View File
@@ -19,6 +19,7 @@
#import "CDVMobileAccessibility.h"
#import <Cordova/CDVAvailability.h>
#import <MediaAccessibility/MediaAccessibility.h>
@interface CDVMobileAccessibility ()
// add any property overrides
@@ -27,6 +28,12 @@
@implementation CDVMobileAccessibility
@synthesize callbackId;
@synthesize commandCallbackId;
@synthesize voiceOverRunning;
@synthesize closeCaptioningEnabled;
@synthesize guidedAccessEnabled;
@synthesize invertColorsEnabled;
@synthesize monoAudioEnabled;
// //////////////////////////////////////////////////
@@ -45,6 +52,8 @@
if ([self settingForKey:setting]) {
// set your setting, other init here
}
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(onPause) name:UIApplicationDidEnterBackgroundNotification object:nil];
}
// //////////////////////////////////////////////////
@@ -52,6 +61,9 @@
- (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
@@ -59,24 +71,126 @@
[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)isVoiceOverRunning:(CDVInvokedUrlCommand*)command
{
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:UIAccessibilityIsVoiceOverRunning()];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
[self.commandDelegate runInBackground:^{
self.voiceOverRunning = UIAccessibilityIsVoiceOverRunning();
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.voiceOverRunning];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}];
}
- (void)isClosedCaptioningEnabled:(CDVInvokedUrlCommand*)command
{
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:UIAccessibilityIsClosedCaptioningEnabled()];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
[self.commandDelegate runInBackground:^{
self.closeCaptioningEnabled = [self getClosedCaptioningEnabledStatus];
CDVPluginResult* result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:self.closeCaptioningEnabled];
[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)postAnnouncementNotification:(CDVInvokedUrlCommand *)command
{
CDVPluginResult* result = nil;
NSString* notificationString = [command.arguments objectAtIndex:0];
if (UIAccessibilityIsVoiceOverRunning() &&
notificationString != nil && [notificationString length] > 0) {
self.commandCallbackId = command.callbackId;
[self.commandDelegate runInBackground:^{
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityAnnouncementDidFinish:) name:UIAccessibilityAnnouncementDidFinishNotification object:nil];
UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, notificationString);
}];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}
}
- (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]];
@@ -88,38 +202,59 @@
/* Get the current mobile accessibility status. */
- (NSDictionary*)getMobileAccessibilityStatus
{
NSMutableDictionary* mobileAccessibilityData = [NSMutableDictionary dictionaryWithCapacity:2];
[mobileAccessibilityData setObject:[NSNumber numberWithBool:UIAccessibilityIsVoiceOverRunning()] forKey:@"isVoiceOverRunning"];
[mobileAccessibilityData setObject:[NSNumber numberWithBool:UIAccessibilityIsClosedCaptioningEnabled()] forKey:@"isClosedCaptioningEnabled"];
self.voiceOverRunning = UIAccessibilityIsVoiceOverRunning();
self.closeCaptioningEnabled = [self getClosedCaptioningEnabledStatus];
self.guidedAccessEnabled = UIAccessibilityIsGuidedAccessEnabled();
self.invertColorsEnabled = UIAccessibilityIsInvertColorsEnabled();
self.monoAudioEnabled = UIAccessibilityIsMonoAudioEnabled();
NSMutableDictionary* mobileAccessibilityData = [NSMutableDictionary dictionaryWithCapacity:5];
[mobileAccessibilityData setObject:[NSNumber numberWithBool:self.voiceOverRunning] forKey:@"isVoiceOverRunning"];
[mobileAccessibilityData setObject:[NSNumber numberWithBool:self.closeCaptioningEnabled] forKey:@"isClosedCaptioningEnabled"];
[mobileAccessibilityData setObject:[NSNumber numberWithBool:self.guidedAccessEnabled] forKey:@"isGuidedAccessEnabled"];
[mobileAccessibilityData setObject:[NSNumber numberWithBool:self.invertColorsEnabled] forKey:@"isInvertColorsEnabled"];
[mobileAccessibilityData setObject:[NSNumber numberWithBool:self.monoAudioEnabled] forKey:@"isMonoAudioEnabled"];
return mobileAccessibilityData;
}
/* turn on MobileAccessibility monitoring*/
/* start MobileAccessibility monitoring */
- (void)start:(CDVInvokedUrlCommand*)command
{
self.callbackId = command.callbackId;
[self.commandDelegate runInBackground:^{
self.callbackId = command.callbackId;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:) name:UIAccessibilityVoiceOverStatusChanged object:nil];
[[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];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mobileAccessibilityStatusChanged:)
name:UIAccessibilityClosedCaptioningStatusDidChangeNotification 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
{
// 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:UIAccessibilityVoiceOverStatusChanged object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIAccessibilityClosedCaptioningStatusDidChangeNotification object:nil];
[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:UIAccessibilityVoiceOverStatusChanged object: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];
}];
}
@end
+86 -12
View File
@@ -1,4 +1,4 @@
/*
cordova.define("com.phonegap.plugin.mobile-accessibility.mobile-accessibility", function(require, exports, module) {/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
@@ -26,58 +26,132 @@ var argscheck = require('cordova/argscheck'),
var MobileAccessibility = function() {
this._isVoiceOverRunning = false;
this._isClosedCaptioningEnabled = false;
this._isGuidedAccessEnabled = false;
this._isInvertColorsEnabled = false;
this._isMonoAudioEnabled = false;
// Create new event handlers on the window (returns a channel instance)
this.channels = {
voiceoverstatuschanged:cordova.addWindowEventHandler("voiceoverstatuschanged"),
closedcaptioningstatusdidchange:cordova.addWindowEventHandler("closedcaptioningstatusdidchange")
closedcaptioningstatusdidchange:cordova.addWindowEventHandler("closedcaptioningstatusdidchange"),
guidedaccessstatusdidchange:cordova.addWindowEventHandler("guidedaccessstatusdidchange"),
invertcolorsstatusdidchange:cordova.addWindowEventHandler("invertcolorsstatusdidchange"),
monoaudiostatusdidchange:cordova.addWindowEventHandler("monoaudiostatusdidchange")
};
for (var key in this.channels) {
this.channels[key].onHasSubscribersChange = MobileAccessibility.onHasSubscribersChange;
}
};
/**
* @private
* @ignore
*/
function handlers() {
return mobileAccessibility.channels.voiceoverstatuschanged.numHandlers +
mobileAccessibility.channels.closedcaptioningstatusdidchange.numHandlers;
mobileAccessibility.channels.closedcaptioningstatusdidchange.numHandlers +
mobileAccessibility.channels.invertcolorsstatusdidchange.numHandlers +
mobileAccessibility.channels.monoaudiostatusdidchange.numHandlers +
mobileAccessibility.channels.guidedaccessstatusdidchange.numHandlers;
};
/**
*
* Event handlers for when callbacks get registered for mobileAccessibility.
* Keep track of how many handlers we have so we can start and stop the native MobileAccessibility listener
* appropriately.
* @private
* @ignore
*/
MobileAccessibility.onHasSubscribersChange = function() {
// If we just registered the first handler, make sure native listener is started.
if (this.numHandlers === 1 && handlers() === 1) {
console.log("MobileAccessibility.onHasSubscribersChange "+handlers());
exec(mobileAccessibility._status, mobileAccessibility._error, "MobileAccessibility", "start", []);
} else if (handlers() === 0) {
exec(null, null, "MobileAccessibility", "stop", []);
}
};
/**
* Asynchronous call to native MobileAccessibility detemine if VoiceOver is running.
* @param {function} callback A callback method to recieve the asynchronous result from the native MobileAccessibility.
*/
MobileAccessibility.prototype.isVoiceOverRunning = function(callback) {
exec(callback, null, "MobileAccessibility", "isVoiceOverRunning", []);
};
/**
* Asynchronous call to native MobileAccessibility to detemine if closed captioning is enabled.
* @param {function} callback A callback method to recieve the asynchronous result from the native MobileAccessibility.
*/
MobileAccessibility.prototype.isClosedCaptioningEnabled = function(callback) {
exec(callback, null, "MobileAccessibility", "isClosedCaptioningEnabled", []);
};
/**
* Callback for mobileAccessibility status
* Asynchronous call to native MobileAccessibility to detemine if the display colors have been inverted.
* @param {function} callback A callback method to recieve the asynchronous result from the native MobileAccessibility.
*/
MobileAccessibility.prototype.isInvertColorsEnabled = function(callback) {
exec(callback, null, "MobileAccessibility", "isInvertColorsEnabled", []);
};
/**
* Asynchronous call to native MobileAccessibility to detemine if mono audio is enabled.
* @param {function} callback A callback method to recieve the asynchronous result from the native MobileAccessibility.
*/
MobileAccessibility.prototype.isMonoAudioEnabled = function(callback) {
exec(callback, null, "MobileAccessibility", "isMonoAudioEnabled", []);
};
/**
* Asynchronous call to native MobileAccessibility to detemine if Guided Access is enabled.
* @param {function} callback A callback method to recieve the asynchronous result from the native MobileAccessibility.
*/
MobileAccessibility.prototype.isGuidedAccessEnabled = function(callback) {
exec(callback, null, "MobileAccessibility", "isGuidedAccessEnabled", []);
};
/**
* Posts an announcement notification with a string for VoiceOver to announce, if it is running.
* @param {string} string A string to be announced by VoiceOver.
* @param {function} callback A callback method to recieve the asynchronous result from the native MobileAccessibility, when the announcement is finished, the function should expect an object containing the stringValue that was voiced and a boolean indicating that the announcement wasSuccessful.
*/
MobileAccessibility.prototype.postAnnouncementNotification = function(string, callback) {
exec(callback, null, "MobileAccessibility", "postAnnouncementNotification", [string]);
};
/**
* Callback from native MobileAccessibility returning an which describes the status of iOS accessibility features.
*
* @param {Object} info keys: isVoiceOverRunning, isClosedCaptioningEnabled
* @param {Object} info
* @config {Boolean} [isVoiceOverRunning] Boolean to indicate VoiceOver status.
* @config {Boolean} [isClosedCaptioningEnabled] Boolean to indicate closed captioning status.
* @config {Boolean} [isGuidedAccessEnabled] Boolean to indicate guided access status.
* @config {Boolean} [isInvertColorsEnabled] Boolean to indicate invert colors status.
* @config {Boolean} [isMonoAudioEnabled] Boolean to indicate mono audio status.
*/
MobileAccessibility.prototype._status = function(info) {
if (info) {
var me = mobileAccessibility;
if (me._isVoiceOverRunning !== info.isVoiceOverRunning) {
if (mobileAccessibility._isVoiceOverRunning !== info.isVoiceOverRunning) {
cordova.fireWindowEvent("voiceoverstatuschanged", info);
me._isVoiceOverRunning = info.isVoiceOverRunning;
mobileAccessibility._isVoiceOverRunning = info.isVoiceOverRunning;
}
if (me._isClosedCaptioningEnabled !== info.isClosedCaptioningEnabled) {
if (mobileAccessibility._isClosedCaptioningEnabled !== info.isClosedCaptioningEnabled) {
cordova.fireWindowEvent("closedcaptioningstatusdidchange", info);
me._isClosedCaptioningEnabled = info.isClosedCaptioningEnabled;
mobileAccessibility._isClosedCaptioningEnabled = info.isClosedCaptioningEnabled;
}
if (mobileAccessibility._isGuidedAccessEnabled !== info.isGuidedAccessEnabled) {
cordova.fireWindowEvent("guidedaccessstatusdidchange", info);
mobileAccessibility._isGuidedAccessEnabled = info.isGuidedAccessEnabled;
}
if (mobileAccessibility._isInvertColorsEnabled !== info.isInvertColorsEnabled) {
cordova.fireWindowEvent("invertcolorsstatusdidchange", info);
mobileAccessibility._isInvertColorsEnabled = info.isInvertColorsEnabled;
}
if (mobileAccessibility._isMonoAudioEnabled !== info.isMonoAudioEnabled) {
cordova.fireWindowEvent("monoaudiostatusdidchange", info);
mobileAccessibility._isMonoAudioEnabled = info.isMonoAudioEnabled;
}
}
};
@@ -92,4 +166,4 @@ MobileAccessibility.prototype._error = function(e) {
var mobileAccessibility = new MobileAccessibility();
module.exports = mobileAccessibility;
start
});