Bug 752935 - Decouple Android a11y checking, prepare for ICS on-the-fly a11y toggle events. r=davidb, r=kats
authorEitan Isaacson <eitan@monotonous.org>
Thu, 10 May 2012 10:33:12 -0700
changeset 93731 ce53253d571777410e856c8da80d32b91e45b204
parent 93730 a8b9ad3204ab0a33d7344cb8d300903c47478dd8
child 93732 6a7bfd84596e65c9d8704a510bd5b6a230fd7558
push id9290
push usereisaacson@mozilla.com
push dateThu, 10 May 2012 17:33:22 +0000
treeherdermozilla-inbound@ce53253d5717 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdavidb, kats
bugs752935
milestone15.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 752935 - Decouple Android a11y checking, prepare for ICS on-the-fly a11y toggle events. r=davidb, r=kats
accessible/src/jsat/AccessFu.jsm
mobile/android/base/GeckoApp.java
mobile/android/base/GeckoAppShell.java
--- a/accessible/src/jsat/AccessFu.jsm
+++ b/accessible/src/jsat/AccessFu.jsm
@@ -43,25 +43,28 @@ var AccessFu = {
     this.prefsBranch.addObserver('accessfu', this, false);
 
     let accessPref = ACCESSFU_DISABLE;
     try {
       accessPref = this.prefsBranch.getIntPref('accessfu');
     } catch (x) {
     }
 
-    if (this.amINeeded(accessPref))
-      this.enable();
+    this.processPreferences(accessPref);
   },
 
   /**
    * Start AccessFu mode, this primarily means controlling the virtual cursor
    * with arrow keys.
    */
   enable: function enable() {
+    if (this._enabled)
+      return;
+    this._enabled = true;
+
     dump('AccessFu enable');
     this.addPresenter(new VisualPresenter());
 
     // Implicitly add the Android presenter on Android.
     if (Services.appinfo.OS == 'Android')
       this.addPresenter(new AndroidPresenter());
 
     VirtualCursorController.attach(this.chromeWin);
@@ -72,50 +75,57 @@ var AccessFu = {
     this.chromeWin.addEventListener('scroll', this, true);
     this.chromeWin.addEventListener('TabOpen', this, true);
   },
 
   /**
    * Disable AccessFu and return to default interaction mode.
    */
   disable: function disable() {
+    if (!this._enabled)
+      return;
+    this._enabled = false;
+
     dump('AccessFu disable');
 
     this.presenters.forEach(function(p) { p.detach(); });
     this.presenters = [];
 
     VirtualCursorController.detach();
 
     Services.obs.removeObserver(this, 'accessible-event');
     this.chromeWin.removeEventListener('DOMActivate', this, true);
     this.chromeWin.removeEventListener('resize', this, true);
     this.chromeWin.removeEventListener('scroll', this, true);
     this.chromeWin.removeEventListener('TabOpen', this, true);
   },
 
-  amINeeded: function(aPref) {
-    switch (aPref) {
-      case ACCESSFU_ENABLE:
-        return true;
-      case ACCESSFU_AUTO:
-        if (Services.appinfo.OS == 'Android') {
-          let msg = Cc['@mozilla.org/android/bridge;1'].
-            getService(Ci.nsIAndroidBridge).handleGeckoMessage(
-              JSON.stringify(
-                { gecko: {
-                    type: 'Accessibility:IsEnabled',
-                    eventType: 1,
-                    text: []
-                  }
-                }));
-          return JSON.parse(msg).enabled;
+  processPreferences: function processPreferences(aPref) {
+    if (Services.appinfo.OS == 'Android') {
+      if (aPref == ACCESSFU_AUTO) {
+        if (!this._observingSystemSettings) {
+          Services.obs.addObserver(this, 'Accessibility:Settings', false);
+          this._observingSystemSettings = true;
         }
-      default:
-        return false;
+        Cc['@mozilla.org/android/bridge;1'].
+          getService(Ci.nsIAndroidBridge).handleGeckoMessage(
+            JSON.stringify({ gecko: { type: 'Accessibility:Ready' } }));
+        return;
+      }
+
+      if (this._observingSystemSettings) {
+        Services.obs.removeObserver(this, 'Accessibility:Settings');
+        this._observingSystemSettings = false;
+      }
     }
+
+    if (aPref == ACCESSFU_ENABLE)
+      this.enable();
+    else
+      this.disable();
   },
 
   addPresenter: function addPresenter(presenter) {
     this.presenters.push(presenter);
     presenter.attach(this.chromeWin);
   },
 
   handleEvent: function handleEvent(aEvent) {
@@ -154,23 +164,25 @@ var AccessFu = {
         this.presenters.forEach(function(p) { p.viewportChanged(); });
         break;
       }
     }
   },
 
   observe: function observe(aSubject, aTopic, aData) {
     switch (aTopic) {
+      case 'Accessibility:Settings':
+        if (JSON.parse(aData).enabled)
+          this.enable();
+        else
+          this.disable();
+        break;
       case 'nsPref:changed':
-        if (aData == 'accessfu') {
-          if (this.amINeeded(this.prefsBranch.getIntPref('accessfu')))
-            this.enable();
-          else
-            this.disable();
-        }
+        if (aData == 'accessfu')
+          this.processPreferences(this.prefsBranch.getIntPref('accessfu'));
         break;
       case 'accessible-event':
         let event;
         try {
           event = aSubject.QueryInterface(Ci.nsIAccessibleEvent);
           this.handleAccEvent(event);
         } catch (ex) {
           dump(ex);
@@ -387,17 +399,23 @@ var AccessFu = {
         newContext.push(newAncestor);
       i++;
     }
 
     return newContext;
   },
 
   // A hash of documents that don't yet have an accessible tree.
-  _pendingDocuments: {}
+  _pendingDocuments: {},
+
+  // So we don't enable/disable twice
+  _enabled: false,
+
+  // Observing accessibility settings
+  _observingSystemSettings: false
 };
 
 function getAccessible(aNode) {
   try {
     return Cc['@mozilla.org/accessibleRetrieval;1'].
       getService(Ci.nsIAccessibleRetrieval).getAccessibleFor(aNode);
   } catch (e) {
     return null;
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -1058,16 +1058,33 @@ abstract public class GeckoApp
 
                 mMainHandler.post(new Runnable() {
                     public void run() {
                         AccessibilityManager accessibilityManager =
                             (AccessibilityManager) mAppContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
                         accessibilityManager.sendAccessibilityEvent(accEvent);
                     }
                 });
+            } else if (event.equals("Accessibility:Ready")) {
+                mMainHandler.post(new Runnable() {
+                    public void run() {
+                        JSONObject ret = new JSONObject();
+                        AccessibilityManager accessibilityManager =
+                            (AccessibilityManager) mAppContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+                        try {
+                            ret.put("enabled", accessibilityManager.isEnabled());
+                            // XXX: A placeholder for future explore by touch support.
+                            ret.put("exploreByTouch", false);
+                        } catch (Exception ex) {
+                            Log.e(LOGTAG, "Error building JSON arguments for Accessibility:Ready:", ex);
+                        }
+                        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Accessibility:Settings",
+                                                                                       ret.toString()));
+                    }
+                });
             }
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception handling message \"" + event + "\":", e);
         }
     }
 
     public void showAboutHome() {
         Runnable r = new AboutHomeRunnable(true);
@@ -1690,16 +1707,17 @@ abstract public class GeckoApp
         GeckoAppShell.registerGeckoEventListener("Permissions:Data", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("CharEncoding:Data", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("CharEncoding:State", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Update:Restart", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Tab:HasTouchListener", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Session:StatePurged", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Bookmark:Insert", GeckoApp.mAppContext);
         GeckoAppShell.registerGeckoEventListener("Accessibility:Event", GeckoApp.mAppContext);
+        GeckoAppShell.registerGeckoEventListener("Accessibility:Ready", GeckoApp.mAppContext);
 
         if (SmsManager.getInstance() != null) {
           SmsManager.getInstance().start();
         }
 
         mBatteryReceiver = new GeckoBatteryManager();
         mBatteryReceiver.registerFor(mAppContext);
 
@@ -2034,16 +2052,18 @@ abstract public class GeckoApp
         GeckoAppShell.unregisterGeckoEventListener("ToggleChrome:Show", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("ToggleChrome:Focus", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("Permissions:Data", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("CharEncoding:Data", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("CharEncoding:State", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("Tab:HasTouchListener", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("Session:StatePurged", GeckoApp.mAppContext);
         GeckoAppShell.unregisterGeckoEventListener("Bookmark:Insert", GeckoApp.mAppContext);
+        GeckoAppShell.unregisterGeckoEventListener("Accessibility:Event", GeckoApp.mAppContext);
+        GeckoAppShell.unregisterGeckoEventListener("Accessibility:Ready", GeckoApp.mAppContext);
 
         if (mFavicons != null)
             mFavicons.close();
 
         if (SmsManager.getInstance() != null) {
             SmsManager.getInstance().stop();
             if (isFinishing())
                 SmsManager.getInstance().shutdown();
--- a/mobile/android/base/GeckoAppShell.java
+++ b/mobile/android/base/GeckoAppShell.java
@@ -68,17 +68,16 @@ import android.content.pm.*;
 import android.graphics.*;
 import android.widget.*;
 import android.hardware.*;
 import android.location.*;
 import android.webkit.MimeTypeMap;
 import android.media.MediaScannerConnection;
 import android.media.MediaScannerConnection.MediaScannerConnectionClient;
 import android.provider.Settings;
-import android.view.accessibility.AccessibilityManager;
 import android.opengl.GLES20;
 
 import android.util.*;
 import android.net.Uri;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 
 import android.graphics.Bitmap;
@@ -1839,26 +1838,16 @@ public class GeckoAppShell
                 try {
                     promptServiceResult = PromptService.waitForReturn();
                 } catch (InterruptedException e) {
                     Log.i(LOGTAG, "showing prompt ",  e);
                 }
                 return promptServiceResult;
             }
 
-            if (type.equals("Accessibility:IsEnabled")) {
-                JSONObject ret = new JSONObject();
-                AccessibilityManager accessibilityManager =
-                    (AccessibilityManager) GeckoApp.mAppContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
-                try {
-                    ret.put("enabled", accessibilityManager.isEnabled());
-                } catch (Exception ex) { }
-                return ret.toString();
-            }
-
             CopyOnWriteArrayList<GeckoEventListener> listeners;
             synchronized (mEventListeners) {
                 listeners = mEventListeners.get(type);
             }
 
             if (listeners == null)
                 return "";