Merge "Refactoring of the Accessibility."
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 033a598..d82b9a3 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -399,6 +399,13 @@
     }
 
     /**
+     * @hide
+     */
+    public void setComponentName(ComponentName component) {
+        mId = component.flattenToShortString();
+    }
+
+    /**
      * The accessibility service id.
      * <p>
      *   <strong>Generated by the system.</strong>
@@ -515,6 +522,33 @@
     }
 
     @Override
+    public int hashCode() {
+        return 31 * 1 + ((mId == null) ? 0 : mId.hashCode());
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        AccessibilityServiceInfo other = (AccessibilityServiceInfo) obj;
+        if (mId == null) {
+            if (other.mId != null) {
+                return false;
+            }
+        } else if (!mId.equals(other.mId)) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
     public String toString() {
         StringBuilder stringBuilder = new StringBuilder();
         appendEventTypes(stringBuilder, eventTypes);
diff --git a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
index c37f51f..179db12 100644
--- a/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -174,7 +174,7 @@
                 mMotionEventSequenceStarted = true;
             }
         } else {
-        // Wait for an enter hover event to start processing.
+            // Wait for an enter hover event to start processing.
             if (!mHoverEventSequenceStarted) {
                 if (motionEvent.getActionMasked() != MotionEvent.ACTION_HOVER_ENTER) {
                     return;
@@ -234,10 +234,14 @@
         if (DEBUG) {
             Slog.i(TAG, "Handling batched event: " + event + ", policyFlags: " + policyFlags);
         }
-        mPm.userActivity(event.getEventTime(), false);
-        MotionEvent transformedEvent = MotionEvent.obtain(event);
-        mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
-        transformedEvent.recycle();
+        // Since we do batch processing it is possible that by the time the
+        // next batch is processed the event handle had been set to null.
+        if (mEventHandler != null) {
+            mPm.userActivity(event.getEventTime(), false);
+            MotionEvent transformedEvent = MotionEvent.obtain(event);
+            mEventHandler.onMotionEvent(transformedEvent, event, policyFlags);
+            transformedEvent.recycle();
+        }
     }
 
     @Override
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index bb040bf..aadb790 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -123,6 +123,9 @@
     private static final String TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED =
             "temporaryEnableAccessibilityStateUntilKeyguardRemoved";
 
+    private static final ComponentName sFakeAccessibilityServiceComponentName =
+            new ComponentName("foo.bar", "FakeService");
+
     private static final String FUNCTION_DUMP = "dump";
 
     private static final char COMPONENT_NAME_SEPARATOR = ':';
@@ -157,8 +160,6 @@
 
     private final MainHandler mMainHandler;
 
-    private Service mUiAutomationService;
-
     private Service mQueryBridge;
 
     private AlertDialog mEnableTouchExplorationDialog;
@@ -167,6 +168,11 @@
 
     private boolean mHasInputFilter;
 
+    private final Set<ComponentName> mTempComponentNameSet = new HashSet<ComponentName>();
+
+    private final List<AccessibilityServiceInfo> mTempAccessibilityServiceInfoList =
+            new ArrayList<AccessibilityServiceInfo>();
+
     private final RemoteCallbackList<IAccessibilityManagerClient> mGlobalClients =
             new RemoteCallbackList<IAccessibilityManagerClient>();
 
@@ -177,9 +183,6 @@
 
     private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
 
-    private final TempUserStateChangeMemento mTempStateChangeForCurrentUserMemento =
-            new TempUserStateChangeMemento();
-
     private int mCurrentUserId = UserHandle.USER_OWNER;
 
     private UserState getCurrentUserStateLocked() {
@@ -224,10 +227,11 @@
                         return;
                     }
                     // We will update when the automation service dies.
-                    if (mUiAutomationService == null) {
-                        UserState userState = getCurrentUserStateLocked();
-                        populateInstalledAccessibilityServiceLocked(userState);
-                        manageServicesLocked(userState);
+                    UserState userState = getCurrentUserStateLocked();
+                    if (userState.mUiAutomationService == null) {
+                        if (readConfigurationForUserStateLocked(userState)) {
+                            onUserStateChangedLocked(userState);
+                        }
                     }
                 }
             }
@@ -239,8 +243,8 @@
                     if (userId != mCurrentUserId) {
                         return;
                     }
-                    UserState state = getUserStateLocked(userId);
-                    Iterator<ComponentName> it = state.mEnabledServices.iterator();
+                    UserState userState = getUserStateLocked(userId);
+                    Iterator<ComponentName> it = userState.mEnabledServices.iterator();
                     while (it.hasNext()) {
                         ComponentName comp = it.next();
                         String compPkg = comp.getPackageName();
@@ -249,13 +253,17 @@
                             // Update the enabled services setting.
                             persistComponentNamesToSettingLocked(
                                     Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                                    state.mEnabledServices, userId);
+                                    userState.mEnabledServices, userId);
                             // Update the touch exploration granted services setting.
-                            state.mTouchExplorationGrantedServices.remove(comp);
+                            userState.mTouchExplorationGrantedServices.remove(comp);
                             persistComponentNamesToSettingLocked(
                                     Settings.Secure.
                                     TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
-                                    state.mEnabledServices, userId);
+                                    userState.mTouchExplorationGrantedServices, userId);
+                            // We will update when the automation service dies.
+                            if (userState.mUiAutomationService == null) {
+                                onUserStateChangedLocked(userState);
+                            }
                             return;
                         }
                     }
@@ -270,8 +278,8 @@
                     if (userId != mCurrentUserId) {
                         return false;
                     }
-                    UserState state = getUserStateLocked(userId);
-                    Iterator<ComponentName> it = state.mEnabledServices.iterator();
+                    UserState userState = getUserStateLocked(userId);
+                    Iterator<ComponentName> it = userState.mEnabledServices.iterator();
                     while (it.hasNext()) {
                         ComponentName comp = it.next();
                         String compPkg = comp.getPackageName();
@@ -283,7 +291,11 @@
                                 it.remove();
                                 persistComponentNamesToSettingLocked(
                                         Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                                        state.mEnabledServices, userId);
+                                        userState.mEnabledServices, userId);
+                                // We will update when the automation service dies.
+                                if (userState.mUiAutomationService == null) {
+                                    onUserStateChangedLocked(userState);
+                                }
                             }
                         }
                     }
@@ -310,7 +322,13 @@
                 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                     removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
                 } else if (Intent.ACTION_USER_PRESENT.equals(action)) {
-                    restoreStateFromMementoIfNeeded();
+                    // We will update when the automation service dies.
+                    UserState userState = getCurrentUserStateLocked();
+                    if (userState.mUiAutomationService == null) {
+                        if (readConfigurationForUserStateLocked(userState)) {
+                            onUserStateChangedLocked(userState);
+                        }
+                    }
                 }
             }
         }, UserHandle.ALL, intentFilter, null, null);
@@ -329,7 +347,7 @@
                 if (DEBUG) {
                     Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
                 }
-                return getClientState(userState);
+                return userState.getClientState();
             } else {
                 userState.mClients.register(client);
                 // If this client is not for the current user we do not
@@ -339,7 +357,7 @@
                     Slog.i(LOG_TAG, "Added user client for pid:" + Binder.getCallingPid()
                             + " and userId:" + mCurrentUserId);
                 }
-                return (resolvedUserId == mCurrentUserId) ? getClientState(userState) : 0;
+                return (resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0;
             }
         }
     }
@@ -385,7 +403,7 @@
                     .resolveCallingUserIdEnforcingPermissionsLocked(userId);
             result = mEnabledServicesForFeedbackTempList;
             result.clear();
-            List<Service> services = getUserStateLocked(resolvedUserId).mServices;
+            List<Service> services = getUserStateLocked(resolvedUserId).mBoundServices;
             while (feedbackType != 0) {
                 final int feedbackTypeBit = (1 << Integer.numberOfTrailingZeros(feedbackType));
                 feedbackType &= ~feedbackTypeBit;
@@ -410,7 +428,7 @@
             if (resolvedUserId != mCurrentUserId) {
                 return;
             }
-            services = getUserStateLocked(resolvedUserId).mServices;
+            services = getUserStateLocked(resolvedUserId).mBoundServices;
         }
         for (int i = 0, count = services.size(); i < count; i++) {
             Service service = services.get(i);
@@ -514,36 +532,48 @@
             AccessibilityServiceInfo accessibilityServiceInfo) {
         mSecurityPolicy.enforceCallingPermission(Manifest.permission.RETRIEVE_WINDOW_CONTENT,
                 FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE);
-        ComponentName componentName = new ComponentName("foo.bar",
-                "AutomationAccessibilityService");
+
+        accessibilityServiceInfo.setComponentName(sFakeAccessibilityServiceComponentName);
+
         synchronized (mLock) {
-            if (mUiAutomationService != null) {
+            UserState userState = getCurrentUserStateLocked();
+
+            if (userState.mUiAutomationService != null) {
                 throw new IllegalStateException("UiAutomationService " + serviceClient
                         + "already registered!");
             }
-            // If an automation services is connected to the system all services are stopped
-            // so the automation one is the only one running. Settings are not changed so when
-            // the automation service goes away the state is restored from the settings.
-            UserState userState = getCurrentUserStateLocked();
-            unbindAllServicesLocked(userState);
 
-            // If necessary enable accessibility and announce that.
-            if (!userState.mIsAccessibilityEnabled) {
-                userState.mIsAccessibilityEnabled = true;
-            }
-            // No touch exploration.
+            userState.mUiAutomationServiceClient = serviceClient;
+
+            // Set the temporary state.
+            userState.mIsAccessibilityEnabled = true;
             userState.mIsTouchExplorationEnabled = false;
-
-            // No touch enhanced web accessibility.
             userState.mIsEnhancedWebAccessibilityEnabled = false;
+            userState.mIsDisplayMagnificationEnabled = false;
+            userState.mInstalledServices.add(accessibilityServiceInfo);
+            userState.mEnabledServices.clear();
+            userState.mEnabledServices.add(sFakeAccessibilityServiceComponentName);
+            userState.mTouchExplorationGrantedServices.add(sFakeAccessibilityServiceComponentName);
 
-            // Hook the automation service up.
-            mUiAutomationService = new Service(mCurrentUserId, componentName,
-                    accessibilityServiceInfo, true);
-            mUiAutomationService.onServiceConnected(componentName, serviceClient.asBinder());
+            // Use the new state instead of settings.
+            onUserStateChangedLocked(userState);
+        }
+    }
 
-            scheduleUpdateInputFilter(userState);
-            scheduleSendStateToClientsLocked(userState);
+    public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
+        synchronized (mLock) {
+            UserState userState = getCurrentUserStateLocked();
+            // Automation service is not bound, so pretend it died to perform clean up.
+            if (userState.mUiAutomationService != null
+                    && userState.mUiAutomationService.mServiceInterface != null
+                    && serviceClient != null
+                    && userState.mUiAutomationService.mServiceInterface.asBinder()
+                    == serviceClient.asBinder()) {
+                userState.mUiAutomationService.binderDied();
+            } else {
+                throw new IllegalStateException("UiAutomationService " + serviceClient
+                        + " not registered!");
+            }
         }
     }
 
@@ -560,36 +590,26 @@
             return;
         }
         synchronized (mLock) {
-            UserState userState = getCurrentUserStateLocked();
-            // Stash the old state so we can restore it when the keyguard is gone.
-            mTempStateChangeForCurrentUserMemento.initialize(getCurrentUserStateLocked());
             // Set the temporary state.
+            UserState userState = getCurrentUserStateLocked();
+
+            // This is a nop if UI automation is enabled.
+            if (userState.mUiAutomationService != null) {
+                return;
+            }
+
             userState.mIsAccessibilityEnabled = true;
             userState.mIsTouchExplorationEnabled = touchExplorationEnabled;
             userState.mIsEnhancedWebAccessibilityEnabled = false;
             userState.mIsDisplayMagnificationEnabled = false;
             userState.mEnabledServices.clear();
             userState.mEnabledServices.add(service);
+            userState.mBindingServices.clear();
             userState.mTouchExplorationGrantedServices.clear();
             userState.mTouchExplorationGrantedServices.add(service);
-            // Update the internal state.
-            performServiceManagementLocked(userState);
-            scheduleUpdateInputFilter(userState);
-            scheduleSendStateToClientsLocked(userState);
-        }
-    }
 
-    public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
-        synchronized (mLock) {
-            // Automation service is not bound, so pretend it died to perform clean up.
-            if (mUiAutomationService != null && mUiAutomationService.mServiceInterface != null
-                    && serviceClient != null && mUiAutomationService.mServiceInterface
-                            .asBinder() == serviceClient.asBinder()) {
-                mUiAutomationService.binderDied();
-            } else {
-                throw new IllegalStateException("UiAutomationService " + serviceClient
-                        + " not registered!");
-            }
+            // User the current state instead settings.
+            onUserStateChangedLocked(userState);
         }
     }
 
@@ -695,10 +715,6 @@
 
     private void switchUser(int userId) {
         synchronized (mLock) {
-            // The user switched so we do not need to restore the current user
-            // state since we will fully rebuild it when he becomes current again.
-            mTempStateChangeForCurrentUserMemento.clear();
-
             // Disconnect from services for the old user.
             UserState oldUserState = getUserStateLocked(mCurrentUserId);
             unbindAllServicesLocked(oldUserState);
@@ -716,9 +732,14 @@
             // The user changed.
             mCurrentUserId = userId;
 
-            // Recreate the internal state for the new user.
-            mMainHandler.obtainMessage(MainHandler.MSG_SEND_RECREATE_INTERNAL_STATE,
-                    mCurrentUserId, 0).sendToTarget();
+            UserState userState = getCurrentUserStateLocked();
+            if (userState.mUiAutomationService != null) {
+                // Switching users disables the UI automation service.
+                userState.mUiAutomationService.binderDied();
+            } else if (readConfigurationForUserStateLocked(userState)) {
+                // Update the user state if needed.
+               onUserStateChangedLocked(userState);
+            }
 
             if (announceNewUser) {
                 // Schedule announcement of the current user if needed.
@@ -734,25 +755,11 @@
         }
     }
 
-    private void restoreStateFromMementoIfNeeded() {
-        synchronized (mLock) {
-            if (mTempStateChangeForCurrentUserMemento.mUserId != UserHandle.USER_NULL) {
-                UserState userState = getCurrentUserStateLocked();
-                // Restore the state from the memento.
-                mTempStateChangeForCurrentUserMemento.applyTo(userState);
-                mTempStateChangeForCurrentUserMemento.clear();
-                // Update the internal state.
-                performServiceManagementLocked(userState);
-                scheduleUpdateInputFilter(userState);
-                scheduleSendStateToClientsLocked(userState);
-            }
-        }
-    }
-
     private Service getQueryBridge() {
         if (mQueryBridge == null) {
             AccessibilityServiceInfo info = new AccessibilityServiceInfo();
-            mQueryBridge = new Service(UserHandle.USER_NULL, null, info, true);
+            mQueryBridge = new Service(UserHandle.USER_NULL,
+                    sFakeAccessibilityServiceComponentName, info);
         }
         return mQueryBridge;
     }
@@ -768,8 +775,8 @@
         //       behavior is observed from different combinations of
         //       enabled accessibility services.
         UserState state = getCurrentUserStateLocked();
-        for (int i = state.mServices.size() - 1; i >= 0; i--) {
-            Service service = state.mServices.get(i);
+        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+            Service service = state.mBoundServices.get(i);
             if (service.mRequestTouchExplorationMode && service.mIsDefault == isDefault) {
                 service.notifyGesture(gestureId);
                 return true;
@@ -780,8 +787,8 @@
 
     private void notifyClearAccessibilityNodeInfoCacheLocked() {
         UserState state = getCurrentUserStateLocked();
-        for (int i = state.mServices.size() - 1; i >= 0; i--) {
-            Service service = state.mServices.get(i);
+        for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
+            Service service = state.mBoundServices.get(i);
             service.notifyClearAccessibilityNodeInfoCache();
         }
     }
@@ -807,8 +814,8 @@
         }
     }
 
-    private void populateInstalledAccessibilityServiceLocked(UserState userState) {
-        userState.mInstalledServices.clear();
+    private boolean readInstalledAccessibilityServiceLocked(UserState userState) {
+        mTempAccessibilityServiceInfoList.clear();
 
         List<ResolveInfo> installedServices = mPackageManager.queryIntentServicesAsUser(
                 new Intent(AccessibilityService.SERVICE_INTERFACE),
@@ -829,26 +836,53 @@
             AccessibilityServiceInfo accessibilityServiceInfo;
             try {
                 accessibilityServiceInfo = new AccessibilityServiceInfo(resolveInfo, mContext);
-                userState.mInstalledServices.add(accessibilityServiceInfo);
+                mTempAccessibilityServiceInfoList.add(accessibilityServiceInfo);
             } catch (XmlPullParserException xppe) {
                 Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", xppe);
             } catch (IOException ioe) {
                 Slog.e(LOG_TAG, "Error while initializing AccessibilityServiceInfo", ioe);
             }
         }
+
+        if (!mTempAccessibilityServiceInfoList.equals(userState.mInstalledServices)) {
+            userState.mInstalledServices.clear();
+            userState.mInstalledServices.addAll(mTempAccessibilityServiceInfoList);
+            mTempAccessibilityServiceInfoList.clear();
+            return true;
+        }
+
+        mTempAccessibilityServiceInfoList.clear();
+        return false;
     }
 
-    private void populateEnabledAccessibilityServicesLocked(UserState userState) {
-        populateComponentNamesFromSettingLocked(
-                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
-                userState.mUserId,
-                userState.mEnabledServices);
+    private boolean readEnabledAccessibilityServicesLocked(UserState userState) {
+        mTempComponentNameSet.clear();
+        readComponentNamesFromSettingLocked(Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                userState.mUserId, mTempComponentNameSet);
+        if (!mTempComponentNameSet.equals(userState.mEnabledServices)) {
+            userState.mEnabledServices.clear();
+            userState.mEnabledServices.addAll(mTempComponentNameSet);
+            mTempComponentNameSet.clear();
+            return true;
+        }
+        mTempComponentNameSet.clear();
+        return false;
     }
 
-    private void populateTouchExplorationGrantedAccessibilityServicesLocked(UserState userState) {
-        populateComponentNamesFromSettingLocked(
+    private boolean readTouchExplorationGrantedAccessibilityServicesLocked(
+            UserState userState) {
+        mTempComponentNameSet.clear();
+        readComponentNamesFromSettingLocked(
                 Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
-                userState.mUserId, userState.mTouchExplorationGrantedServices);
+                userState.mUserId, mTempComponentNameSet);
+        if (!mTempComponentNameSet.equals(userState.mTouchExplorationGrantedServices)) {
+            userState.mTouchExplorationGrantedServices.clear();
+            userState.mTouchExplorationGrantedServices.addAll(mTempComponentNameSet);
+            mTempComponentNameSet.clear();
+            return true;
+        }
+        mTempComponentNameSet.clear();
+        return false;
     }
 
     /**
@@ -862,8 +896,8 @@
             boolean isDefault) {
         try {
             UserState state = getCurrentUserStateLocked();
-            for (int i = 0, count = state.mServices.size(); i < count; i++) {
-                Service service = state.mServices.get(i);
+            for (int i = 0, count = state.mBoundServices.size(); i < count; i++) {
+                Service service = state.mBoundServices.get(i);
 
                 if (service.mIsDefault == isDefault) {
                     if (canDispathEventLocked(service, event, state.mHandledFeedbackTypes)) {
@@ -880,25 +914,12 @@
         }
     }
 
-    /**
-     * Adds a service for a user.
-     *
-     * @param service The service to add.
-     * @param userId The user id.
-     */
-    private void tryAddServiceLocked(Service service, int userId) {
+    private void addServiceLocked(Service service, UserState userState) {
         try {
-            UserState userState = getUserStateLocked(userId);
-            if (userState.mServices.contains(service)) {
-                return;
-            }
             service.linkToOwnDeath();
-            userState.mServices.add(service);
+            userState.mBoundServices.add(service);
             userState.mComponentNameToServiceMap.put(service.mComponentName, service);
-            scheduleUpdateInputFilter(userState);
-            tryEnableTouchExplorationLocked(service);
-            tryEnableEnhancedWebAccessibilityLocked(service);
-        } catch (RemoteException e) {
+        } catch (RemoteException re) {
             /* do nothing */
         }
     }
@@ -909,19 +930,12 @@
      * @param service The service.
      * @return True if the service was removed, false otherwise.
      */
-    private boolean tryRemoveServiceLocked(Service service) {
+    private void removeServiceLocked(Service service) {
         UserState userState = getUserStateLocked(service.mUserId);
-        final boolean removed = userState.mServices.remove(service);
-        if (!removed) {
-            return false;
-        }
+        userState.mBoundServices.remove(service);
         userState.mComponentNameToServiceMap.remove(service.mComponentName);
         service.unlinkToOwnDeath();
         service.dispose();
-        scheduleUpdateInputFilter(userState);
-        tryDisableTouchExplorationLocked(service);
-        tryDisableEnhancedWebAccessibilityLocked(service);
-        return removed;
     }
 
     /**
@@ -969,29 +983,11 @@
         return false;
     }
 
-    /**
-     * Manages services by starting enabled ones and stopping disabled ones.
-     */
-    private void manageServicesLocked(UserState userState) {
-        final int enabledInstalledServicesCount = updateServicesStateLocked(userState);
-        // No enabled installed services => disable accessibility to avoid
-        // sending accessibility events with no recipient across processes.
-        if (userState.mIsAccessibilityEnabled && enabledInstalledServicesCount == 0) {
-            Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId);
-        }
-    }
-
-    /**
-     * Unbinds all bound services for a user.
-     *
-     * @param userState The user state.
-     */
     private void unbindAllServicesLocked(UserState userState) {
-        List<Service> services = userState.mServices;
+        List<Service> services = userState.mBoundServices;
         for (int i = 0, count = services.size(); i < count; i++) {
             Service service = services.get(i);
-            if (service.unbind()) {
+            if (service.unbindLocked()) {
                 i--;
                 count--;
             }
@@ -1006,7 +1002,7 @@
      * @param userId The user id.
      * @param outComponentNames The output component names.
      */
-    private void populateComponentNamesFromSettingLocked(String settingName, int userId,
+    private void readComponentNamesFromSettingLocked(String settingName, int userId,
             Set<ComponentName> outComponentNames) {
         String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(),
                 settingName, userId);
@@ -1047,19 +1043,11 @@
                 settingName, builder.toString(), userId);
     }
 
-    /**
-     * Updates the state of each service by starting (or keeping running) enabled ones and
-     * stopping the rest.
-     *
-     * @param userState The user state for which to do that.
-     * @return The number of enabled installed services.
-     */
-    private int updateServicesStateLocked(UserState userState) {
+    private void manageServicesLocked(UserState userState) {
         Map<ComponentName, Service> componentNameToServiceMap =
                 userState.mComponentNameToServiceMap;
         boolean isEnabled = userState.mIsAccessibilityEnabled;
 
-        int enabledInstalledServices = 0;
         for (int i = 0, count = userState.mInstalledServices.size(); i < count; i++) {
             AccessibilityServiceInfo installedService = userState.mInstalledServices.get(i);
             ComponentName componentName = ComponentName.unflattenFromString(
@@ -1067,32 +1055,46 @@
             Service service = componentNameToServiceMap.get(componentName);
 
             if (isEnabled) {
+                // Wait for the binding if it is in process.
+                if (userState.mBindingServices.contains(componentName)) {
+                    continue;
+                }
+                // No enabled installed services => disable accessibility to avoid
+                // sending accessibility events with no recipient across processes.
+                if (userState.mEnabledServices.isEmpty()) {
+                    userState.mIsAccessibilityEnabled = false;
+                    Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                            Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId);
+                    return;
+                }
                 if (userState.mEnabledServices.contains(componentName)) {
                     if (service == null) {
-                        service = new Service(userState.mUserId, componentName,
-                                installedService, false);
+                        service = new Service(userState.mUserId, componentName, installedService);
+                    } else if (userState.mBoundServices.contains(service)) {
+                        continue;
                     }
-                    service.bind();
-                    enabledInstalledServices++;
+                    service.bindLocked();
                 } else {
                     if (service != null) {
-                        service.unbind();
+                        service.unbindLocked();
                     }
                 }
             } else {
                 if (service != null) {
-                    service.unbind();
+                    service.unbindLocked();
+                } else {
+                    userState.mBindingServices.remove(componentName);
                 }
             }
         }
-
-        return enabledInstalledServices;
     }
 
-    private void scheduleSendStateToClientsLocked(UserState userState) {
-        if (mGlobalClients.getRegisteredCallbackCount() > 0
-                || userState.mClients.getRegisteredCallbackCount() > 0) {
-            final int clientState = getClientState(userState);
+    private void scheduleUpdateClientsIfNeededLocked(UserState userState) {
+        final int clientState = userState.getClientState();
+        if (userState.mLastSentClientState != clientState
+                && (mGlobalClients.getRegisteredCallbackCount() > 0
+                        || userState.mClients.getRegisteredCallbackCount() > 0)) {
+            userState.mLastSentClientState = clientState;
             mMainHandler.obtainMessage(MainHandler.MSG_SEND_STATE_TO_CLIENTS,
                     clientState, userState.mUserId) .sendToTarget();
         }
@@ -1144,10 +1146,10 @@
     }
 
     private void showEnableTouchExplorationDialog(final Service service) {
-        String label = service.mResolveInfo.loadLabel(
-
-        mContext.getPackageManager()).toString();
         synchronized (mLock) {
+            String label = service.mResolveInfo.loadLabel(
+            mContext.getPackageManager()).toString();
+
             final UserState state = getCurrentUserStateLocked();
             if (state.mIsTouchExplorationEnabled) {
                 return;
@@ -1167,9 +1169,12 @@
                                  Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
                                  state.mTouchExplorationGrantedServices, state.mUserId);
                          // Enable touch exploration.
+                         UserState userState = getUserStateLocked(service.mUserId);
+                         userState.mIsTouchExplorationEnabled = true;
                          Settings.Secure.putIntForUser(mContext.getContentResolver(),
                                  Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1,
                                  service.mUserId);
+                         onUserStateChangedLocked(userState);
                      }
                  })
                  .setNegativeButton(android.R.string.cancel, new OnClickListener() {
@@ -1191,92 +1196,104 @@
         }
     }
 
-    private int getClientState(UserState userState) {
-        int clientState = 0;
-        if (userState.mIsAccessibilityEnabled) {
-            clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
-        }
-        // Touch exploration relies on enabled accessibility.
-        if (userState.mIsAccessibilityEnabled && userState.mIsTouchExplorationEnabled) {
-            clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
-        }
-        return clientState;
-    }
-
-    private void recreateInternalStateLocked(UserState userState) {
-        populateInstalledAccessibilityServiceLocked(userState);
-        populateEnabledAccessibilityServicesLocked(userState);
-        populateTouchExplorationGrantedAccessibilityServicesLocked(userState);
-        populatedEnhancedWebAccessibilityEnabledChangedLocked(userState);
-
-        handleTouchExplorationEnabledSettingChangedLocked(userState);
-        handleDisplayMagnificationEnabledSettingChangedLocked(userState);
-        handleAccessibilityEnabledSettingChangedLocked(userState);
-        handleTouchExplorationGrantedAccessibilityServicesChangedLocked(userState);
-
-        performServiceManagementLocked(userState);
-
+    private void onUserStateChangedLocked(UserState userState) {
+        updateServicesLocked(userState);
+        updateTouchExplorationLocked(userState);
+        updateEnhancedWebAccessibilityLocked(userState);
         scheduleUpdateInputFilter(userState);
-        scheduleSendStateToClientsLocked(userState);
+        scheduleUpdateClientsIfNeededLocked(userState);
     }
 
-    private void handleAccessibilityEnabledSettingChangedLocked(UserState userState) {
-        userState.mIsAccessibilityEnabled = Settings.Secure.getIntForUser(
-               mContext.getContentResolver(),
-               Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId) == 1;
-    }
-
-    private void performServiceManagementLocked(UserState userState) {
-        if (userState.mIsAccessibilityEnabled ) {
+    private void updateServicesLocked(UserState userState) {
+        if (userState.mIsAccessibilityEnabled) {
             manageServicesLocked(userState);
         } else {
             unbindAllServicesLocked(userState);
         }
     }
 
-    private void handleTouchExplorationEnabledSettingChangedLocked(UserState userState) {
-        userState.mIsTouchExplorationEnabled = Settings.Secure.getIntForUser(
-                mContext.getContentResolver(),
-                Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId) == 1;
+    private boolean readConfigurationForUserStateLocked(UserState userState) {
+        boolean somthingChanged = false;
+        somthingChanged |= readAccessibilityEnabledSettingLocked(userState);
+        somthingChanged |= readInstalledAccessibilityServiceLocked(userState);
+        somthingChanged |= readEnabledAccessibilityServicesLocked(userState);
+        somthingChanged |= readTouchExplorationGrantedAccessibilityServicesLocked(userState);
+        somthingChanged |= readTouchExplorationEnabledSettingLocked(userState);
+        somthingChanged |= readEnhancedWebAccessibilityEnabledChangedLocked(userState);
+        somthingChanged |= readDisplayMagnificationEnabledSettingLocked(userState);
+        return somthingChanged;
     }
 
-    private void handleDisplayMagnificationEnabledSettingChangedLocked(UserState userState) {
-        userState.mIsDisplayMagnificationEnabled = Settings.Secure.getIntForUser(
+    private boolean readAccessibilityEnabledSettingLocked(UserState userState) {
+        final boolean accessibilityEnabled = Settings.Secure.getIntForUser(
+               mContext.getContentResolver(),
+               Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId) == 1;
+        if (accessibilityEnabled != userState.mIsAccessibilityEnabled) {
+            userState.mIsAccessibilityEnabled = accessibilityEnabled;
+            return true;
+        }
+        return false;
+    }
+
+    private boolean readTouchExplorationEnabledSettingLocked(UserState userState) {
+        final boolean touchExplorationEnabled = Settings.Secure.getIntForUser(
+                mContext.getContentResolver(),
+                Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId) == 1;
+        if (touchExplorationEnabled != userState.mIsTouchExplorationEnabled) {
+            userState.mIsTouchExplorationEnabled = touchExplorationEnabled;
+            return true;
+        }
+        return false;
+    }
+
+    private boolean readDisplayMagnificationEnabledSettingLocked(UserState userState) {
+        final boolean displayMagnificationEnabled = Settings.Secure.getIntForUser(
                 mContext.getContentResolver(),
                 Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
                 0, userState.mUserId) == 1;
+        if (displayMagnificationEnabled != userState.mIsDisplayMagnificationEnabled) {
+            userState.mIsDisplayMagnificationEnabled = displayMagnificationEnabled;
+            return true;
+        }
+        return false;
     }
 
-    private void handleTouchExplorationGrantedAccessibilityServicesChangedLocked(
-            UserState userState) {
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId);
-        final int serviceCount = userState.mServices.size();
+    private boolean readEnhancedWebAccessibilityEnabledChangedLocked(UserState userState) {
+         final boolean enhancedWeAccessibilityEnabled = Settings.Secure.getIntForUser(
+                mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
+                0, userState.mUserId) == 1;
+         if (enhancedWeAccessibilityEnabled != userState.mIsEnhancedWebAccessibilityEnabled) {
+             userState.mIsEnhancedWebAccessibilityEnabled = enhancedWeAccessibilityEnabled;
+             return true;
+         }
+         return false;
+    }
+
+    private void updateTouchExplorationLocked(UserState userState) {
+        userState.mIsTouchExplorationEnabled = false;
+        final int serviceCount = userState.mBoundServices.size();
         for (int i = 0; i < serviceCount; i++) {
-            Service service = userState.mServices.get(i);
-            tryEnableTouchExplorationLocked(service);
+            Service service = userState.mBoundServices.get(i);
+            if (tryEnableTouchExplorationLocked(service)) {
+                break;
+            }
         }
     }
 
-    private void populatedEnhancedWebAccessibilityEnabledChangedLocked(UserState userState) {
-        userState.mIsEnhancedWebAccessibilityEnabled = Settings.Secure.getIntForUser(
-                mContext.getContentResolver(), Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
-                0, userState.mUserId) == 1;
-    }
-
-    private void tryEnableTouchExplorationLocked(Service service) {
+    private boolean tryEnableTouchExplorationLocked(Service service) {
         if (!service.canReceiveEventsLocked() || !service.mRequestTouchExplorationMode) {
-            return;
+            return false;
         }
         UserState userState = getUserStateLocked(service.mUserId);
         if (userState.mIsTouchExplorationEnabled) {
-            return;
+            return false;
         }
         // UI test automation service can always enable it.
         if (service.mIsAutomation) {
+            userState.mIsTouchExplorationEnabled = true;
             Settings.Secure.putIntForUser(mContext.getContentResolver(),
                     Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1, service.mUserId);
-            return;
+            return true;
         }
         if (service.mResolveInfo.serviceInfo.applicationInfo.targetSdkVersion
                 <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
@@ -1287,11 +1304,15 @@
                 if (mEnableTouchExplorationDialog == null
                         || (mEnableTouchExplorationDialog != null
                             && !mEnableTouchExplorationDialog.isShowing())) {
-                    showEnableTouchExplorationDialog(service);
+                    mMainHandler.obtainMessage(
+                            MainHandler.MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG,
+                            service).sendToTarget();
                 }
             } else {
+                userState.mIsTouchExplorationEnabled = true;
                 Settings.Secure.putIntForUser(mContext.getContentResolver(),
                         Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1, service.mUserId);
+                return true;
             }
         } else {
             // Starting in JB-MR2 we request a permission to allow a service to enable
@@ -1299,94 +1320,43 @@
             if (mContext.getPackageManager().checkPermission(
                     android.Manifest.permission.CAN_REQUEST_TOUCH_EXPLORATION_MODE,
                     service.mComponentName.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
+                userState.mIsTouchExplorationEnabled = true;
                 Settings.Secure.putIntForUser(mContext.getContentResolver(),
                         Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1, service.mUserId);
+                return true;
             }
         }
+        return false;
     }
 
-    private void tryDisableTouchExplorationLocked(Service service) {
-        if (!service.mRequestTouchExplorationMode) {
-            return;
-        }
-        UserState userState = getUserStateLocked(service.mUserId);
-        if (!userState.mIsTouchExplorationEnabled) {
-            return;
-        }
-        final int serviceCount = userState.mServices.size();
+    private void updateEnhancedWebAccessibilityLocked(UserState userState) {
+        userState.mIsEnhancedWebAccessibilityEnabled = false;
+        final int serviceCount = userState.mBoundServices.size();
         for (int i = 0; i < serviceCount; i++) {
-            Service other = userState.mServices.get(i);
-            if (other != service) {
-                if (service.mResolveInfo.serviceInfo.applicationInfo.targetSdkVersion
-                        <= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-                    // Up to JB-MR1 we had a white list with services that can enable touch
-                    // exploration. When a service is first started we show a dialog to the
-                    // use to get a permission to white list the service.
-                    if (other.mRequestTouchExplorationMode &&
-                            userState.mTouchExplorationGrantedServices.contains(
-                                    service.mComponentName)) {
-                        // A white-listed service wants touch exploration, do not disable.
-                        return;
-                    }
-                } else {
-                    // Starting in JB-MR2 we request a permission to allow a service to enable
-                    // touch exploration and do not care if the service is in the white list.
-                    if (other.mRequestTouchExplorationMode && (service.mIsAutomation
-                            || mContext.getPackageManager().checkPermission(
-                                    android.Manifest.permission.CAN_REQUEST_TOUCH_EXPLORATION_MODE,
-                                    service.mComponentName.getPackageName())
-                                    == PackageManager.PERMISSION_GRANTED)) {
-                        // A service with permission wants touch exploration, do not disable.
-                        return;
-                    }
-                }
-            }
-        }
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.TOUCH_EXPLORATION_ENABLED, 0, userState.mUserId);
-    }
-
-    private void tryEnableEnhancedWebAccessibilityLocked(Service service) {
-        if (!service.canReceiveEventsLocked() || !service.mRequestEnhancedWebAccessibility ) {
-            return;
-        }
-        UserState userState = getUserStateLocked(service.mUserId);
-        if (userState.mIsEnhancedWebAccessibilityEnabled) {
-            return;
-        }
-        // Requested and can enabled, do it.
-        if (service.mRequestEnhancedWebAccessibility
-                && canEnabledEnhancedWebAccessibility(service)) {
-            Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                    Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 1, userState.mUserId);
-        }
-    }
-
-    private void tryDisableEnhancedWebAccessibilityLocked(Service service) {
-        if (!service.mRequestEnhancedWebAccessibility) {
-            return;
-        }
-        UserState userState = getUserStateLocked(service.mUserId);
-        if (!userState.mIsEnhancedWebAccessibilityEnabled) {
-            return;
-        }
-        final int serviceCount = userState.mServices.size();
-        for (int i = 0; i < serviceCount; i++) {
-            Service other = userState.mServices.get(i);
-            if (other != service && other.mRequestEnhancedWebAccessibility
-                    && canEnabledEnhancedWebAccessibility(other)) {
-                // One service requests the feature, do not disable.
+            Service service = userState.mBoundServices.get(i);
+            if (tryEnableEnhancedWebAccessibilityLocked(service)) {
                 return;
             }
         }
-        Settings.Secure.putIntForUser(mContext.getContentResolver(),
-                Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 0, service.mUserId);
     }
 
-    private boolean canEnabledEnhancedWebAccessibility(Service service) {
-        return (service.mIsAutomation || mContext.getPackageManager().checkPermission(
+    private boolean tryEnableEnhancedWebAccessibilityLocked(Service service) {
+        if (!service.canReceiveEventsLocked() || !service.mRequestEnhancedWebAccessibility ) {
+            return false;
+        }
+        UserState userState = getUserStateLocked(service.mUserId);
+        if (userState.mIsEnhancedWebAccessibilityEnabled) {
+            return false;
+        }
+        if (service.mIsAutomation || mContext.getPackageManager().checkPermission(
                 android.Manifest.permission.CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY,
-                service.mComponentName.getPackageName()) == PackageManager.PERMISSION_GRANTED);
+                service.mComponentName.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
+            userState.mIsEnhancedWebAccessibilityEnabled = true;
+            Settings.Secure.putIntForUser(mContext.getContentResolver(),
+                    Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 1, userState.mUserId);
+            return true;
+        }
+        return false;
     }
 
     @Override
@@ -1395,12 +1365,6 @@
         synchronized (mLock) {
             pw.println("ACCESSIBILITY MANAGER (dumpsys accessibility)");
             pw.println();
-            pw.println("Ui automation service: bound=" + (mUiAutomationService != null));
-            pw.println();
-            if (mUiAutomationService != null) {
-                mUiAutomationService.dump(fd, pw, args);
-                pw.println();
-            }
             final int userCount = mUserStates.size();
             for (int i = 0; i < userCount; i++) {
                 UserState userState = mUserStates.valueAt(i);
@@ -1410,17 +1374,22 @@
                 pw.append(", touchExplorationEnabled=" + userState.mIsTouchExplorationEnabled);
                 pw.append(", displayMagnificationEnabled="
                         + userState.mIsDisplayMagnificationEnabled);
+                if (userState.mUiAutomationService != null) {
+                    pw.append(", ");
+                    userState.mUiAutomationService.dump(fd, pw, args);
+                    pw.println();
+                }
                 pw.append("}");
                 pw.println();
                 pw.append("           services:{");
-                final int serviceCount = userState.mServices.size();
+                final int serviceCount = userState.mBoundServices.size();
                 for (int j = 0; j < serviceCount; j++) {
                     if (j > 0) {
                         pw.append(", ");
                         pw.println();
                         pw.append("                     ");
                     }
-                    Service service = userState.mServices.get(j);
+                    Service service = userState.mBoundServices.get(j);
                     service.dump(fd, pw, args);
                 }
                 pw.println("}]");
@@ -1462,10 +1431,10 @@
         public static final int MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER = 1;
         public static final int MSG_SEND_STATE_TO_CLIENTS = 2;
         public static final int MSG_SEND_CLEARED_STATE_TO_CLIENTS_FOR_USER = 3;
-        public static final int MSG_SEND_RECREATE_INTERNAL_STATE = 4;
-        public static final int MSG_UPDATE_ACTIVE_WINDOW = 5;
-        public static final int MSG_ANNOUNCE_NEW_USER_IF_NEEDED = 6;
-        public static final int MSG_UPDATE_INPUT_FILTER = 7;
+        public static final int MSG_UPDATE_ACTIVE_WINDOW = 4;
+        public static final int MSG_ANNOUNCE_NEW_USER_IF_NEEDED = 5;
+        public static final int MSG_UPDATE_INPUT_FILTER = 6;
+        public static final int MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG = 7;
 
         public MainHandler(Looper looper) {
             super(looper);
@@ -1494,13 +1463,6 @@
                     final int userId = msg.arg1;
                     sendStateToClientsForUser(0, userId);
                 } break;
-                case MSG_SEND_RECREATE_INTERNAL_STATE: {
-                    final int userId = msg.arg1;
-                    synchronized (mLock) {
-                        UserState userState = getUserStateLocked(userId);
-                        recreateInternalStateLocked(userState);
-                    }
-                } break;
                 case MSG_UPDATE_ACTIVE_WINDOW: {
                     final int windowId = msg.arg1;
                     final int eventType = msg.arg2;
@@ -1513,6 +1475,10 @@
                     UserState userState = (UserState) msg.obj;
                     updateInputFilter(userState);
                 } break;
+                case MSG_SHOW_ENABLED_TOUCH_EXPLORATION_DIALOG: {
+                    Service service = (Service) msg.obj;
+                    showEnableTouchExplorationDialog(service);
+                } break;
             }
         }
 
@@ -1641,14 +1607,14 @@
         };
 
         public Service(int userId, ComponentName componentName,
-                AccessibilityServiceInfo accessibilityServiceInfo, boolean isAutomation) {
+                AccessibilityServiceInfo accessibilityServiceInfo) {
             mUserId = userId;
             mResolveInfo = accessibilityServiceInfo.getResolveInfo();
             mId = sIdCounter++;
             mComponentName = componentName;
             mAccessibilityServiceInfo = accessibilityServiceInfo;
-            mIsAutomation = isAutomation;
-            if (!isAutomation) {
+            mIsAutomation = (sFakeAccessibilityServiceComponentName.equals(componentName));
+            if (!mIsAutomation) {
                 mCanRetrieveScreenContent = accessibilityServiceInfo.getCanRetrieveWindowContent();
                 mIntent = new Intent().setComponent(mComponentName);
                 mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
@@ -1688,24 +1654,6 @@
                 mRequestEnhancedWebAccessibility = (info.flags
                            & AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY) != 0;
             }
-
-            // If this service is up and running we may have to enable touch
-            // exploration or enhanced web accessibility, otherwise this will
-            // happen when the service connects.
-            synchronized (mLock) {
-                if (canReceiveEventsLocked()) {
-                    if (mRequestTouchExplorationMode) {
-                        tryEnableTouchExplorationLocked(this);
-                    } else {
-                        tryDisableTouchExplorationLocked(this);
-                    }
-                    if (mRequestEnhancedWebAccessibility) {
-                        tryEnableEnhancedWebAccessibilityLocked(this);
-                    } else {
-                        tryDisableEnhancedWebAccessibilityLocked(this);
-                    }
-                }
-            }
         }
 
         /**
@@ -1713,10 +1661,20 @@
          *
          * @return True if binding is successful.
          */
-        public boolean bind() {
-            if (!mIsAutomation && mService == null) {
-                return mContext.bindServiceAsUser(mIntent, this, Context.BIND_AUTO_CREATE,
-                        new UserHandle(mUserId));
+        public boolean bindLocked() {
+            UserState userState = getUserStateLocked(mUserId);
+            if (!mIsAutomation) {
+                if (mService == null) {
+                    if (mContext.bindServiceAsUser(mIntent, this, Context.BIND_AUTO_CREATE,
+                            new UserHandle(mUserId))) {
+                        userState.mBindingServices.add(mComponentName);
+                    }
+                }
+            } else {
+                userState.mBindingServices.add(mComponentName);
+                mService = userState.mUiAutomationServiceClient.asBinder();
+                onServiceConnected(mComponentName, mService);
+                userState.mUiAutomationService = this;
             }
             return false;
         }
@@ -1727,17 +1685,19 @@
          *
          * @return True if unbinding is successful.
          */
-        public boolean unbind() {
-            if (mService != null) {
-                synchronized (mLock) {
-                    tryRemoveServiceLocked(this);
-                }
-                if (!mIsAutomation) {
-                    mContext.unbindService(this);
-                }
-                return true;
+        public boolean unbindLocked() {
+            if (mService == null) {
+                return false;
             }
-            return false;
+            if (!mIsAutomation) {
+                mContext.unbindService(this);
+            } else {
+                UserState userState = getUserStateLocked(mUserId);
+                userState.mUiAutomationService = null;
+                userState.mUiAutomationServiceClient = null;
+            }
+            removeServiceLocked(this);
+            return true;
         }
 
         public boolean canReceiveEventsLocked() {
@@ -1766,6 +1726,8 @@
                     } else {
                         setDynamicallyConfigurableProperties(info);
                     }
+                    UserState userState = getUserStateLocked(mUserId);
+                    onUserStateChangedLocked(userState);
                 }
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -1774,15 +1736,24 @@
 
         @Override
         public void onServiceConnected(ComponentName componentName, IBinder service) {
-            mService = service;
-            mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
-            try {
-                mServiceInterface.setConnection(this, mId);
-                synchronized (mLock) {
-                    tryAddServiceLocked(this, mUserId);
+            final int connectionId;
+            synchronized (mLock) {
+                connectionId = mId;
+                mService = service;
+                mServiceInterface = IAccessibilityServiceClient.Stub.asInterface(service);
+                UserState userState = getUserStateLocked(mUserId);
+                if (!userState.mBindingServices.contains(mComponentName)) {
+                    binderDied();
+                } else {
+                    userState.mBindingServices.remove(mComponentName);
+                    addServiceLocked(this, userState);
+                    onUserStateChangedLocked(userState);
                 }
+            }
+            try {
+                mServiceInterface.setConnection(this, connectionId);
             } catch (RemoteException re) {
-                Slog.w(LOG_TAG, "Error while setting Controller for service: " + service, re);
+                Slog.w(LOG_TAG, "Error while setting connection for service: " + service, re);
             }
         }
 
@@ -2128,13 +2099,15 @@
         public void binderDied() {
             synchronized (mLock) {
                 // The death recipient is unregistered in tryRemoveServiceLocked
-                tryRemoveServiceLocked(this);
-                // We no longer have an automation service, so restore
-                // the state based on values in the settings database.
+                removeServiceLocked(this);
+                UserState userState = getUserStateLocked(mUserId);
                 if (mIsAutomation) {
-                    mUiAutomationService = null;
-                    recreateInternalStateLocked(getUserStateLocked(mUserId));
+                    // We no longer have an automation service, so restore
+                    // the state based on values in the settings database.
+                    userState.mUiAutomationService = null;
+                    userState.mUiAutomationServiceClient = null;
                 }
+                onUserStateChangedLocked(userState);
             }
         }
 
@@ -2585,7 +2558,7 @@
     private class UserState {
         public final int mUserId;
 
-        public final CopyOnWriteArrayList<Service> mServices = new CopyOnWriteArrayList<Service>();
+        public final CopyOnWriteArrayList<Service> mBoundServices = new CopyOnWriteArrayList<Service>();
 
         public final RemoteCallbackList<IAccessibilityManagerClient> mClients =
             new RemoteCallbackList<IAccessibilityManagerClient>();
@@ -2596,6 +2569,8 @@
         public final List<AccessibilityServiceInfo> mInstalledServices =
                 new ArrayList<AccessibilityServiceInfo>();
 
+        public final Set<ComponentName> mBindingServices = new HashSet<ComponentName>();
+
         public final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>();
 
         public final Set<ComponentName> mTouchExplorationGrantedServices =
@@ -2609,57 +2584,30 @@
 
         public int mHandledFeedbackTypes = 0;
 
+        public int mLastSentClientState;
+
         public boolean mIsAccessibilityEnabled;
         public boolean mIsTouchExplorationEnabled;
         public boolean mIsEnhancedWebAccessibilityEnabled;
         public boolean mIsDisplayMagnificationEnabled;
 
+        private Service mUiAutomationService;
+        private IAccessibilityServiceClient mUiAutomationServiceClient;
+
         public UserState(int userId) {
             mUserId = userId;
         }
-    }
 
-    private class TempUserStateChangeMemento {
-        public int mUserId = UserHandle.USER_NULL;
-        public boolean mIsAccessibilityEnabled;
-        public boolean mIsTouchExplorationEnabled;
-        public boolean mIsEnhancedWebAccessibilityEnabled;
-        public boolean mIsDisplayMagnificationEnabled;
-        public final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>();
-        public final Set<ComponentName> mTouchExplorationGrantedServices =
-                new HashSet<ComponentName>();
-
-        public void initialize(UserState userState) {
-            mUserId = userState.mUserId;
-            mIsAccessibilityEnabled = userState.mIsAccessibilityEnabled;
-            mIsTouchExplorationEnabled = userState.mIsTouchExplorationEnabled;
-            mIsEnhancedWebAccessibilityEnabled = userState.mIsEnhancedWebAccessibilityEnabled;
-            mIsDisplayMagnificationEnabled = userState.mIsDisplayMagnificationEnabled;
-            mEnabledServices.clear();
-            mEnabledServices.addAll(userState.mEnabledServices);
-            mTouchExplorationGrantedServices.clear();
-            mTouchExplorationGrantedServices.addAll(userState.mTouchExplorationGrantedServices);
-        }
-
-        public void applyTo(UserState userState) {
-            userState.mIsAccessibilityEnabled = mIsAccessibilityEnabled;
-            userState.mIsTouchExplorationEnabled = mIsTouchExplorationEnabled;
-            userState.mIsEnhancedWebAccessibilityEnabled = mIsEnhancedWebAccessibilityEnabled;
-            userState.mIsDisplayMagnificationEnabled = mIsDisplayMagnificationEnabled;
-            userState.mEnabledServices.clear();
-            userState.mEnabledServices.addAll(mEnabledServices);
-            userState.mTouchExplorationGrantedServices.clear();
-            userState.mTouchExplorationGrantedServices.addAll(mTouchExplorationGrantedServices);
-        }
-
-        public void clear() {
-            mUserId = UserHandle.USER_NULL;
-            mIsAccessibilityEnabled = false;
-            mIsTouchExplorationEnabled = false;
-            mIsEnhancedWebAccessibilityEnabled = false;
-            mIsDisplayMagnificationEnabled = false;
-            mEnabledServices.clear();
-            mTouchExplorationGrantedServices.clear();
+        public int getClientState() {
+            int clientState = 0;
+            if (mIsAccessibilityEnabled) {
+                clientState |= AccessibilityManager.STATE_FLAG_ACCESSIBILITY_ENABLED;
+            }
+            // Touch exploration relies on enabled accessibility.
+            if (mIsAccessibilityEnabled && mIsTouchExplorationEnabled) {
+                clientState |= AccessibilityManager.STATE_FLAG_TOUCH_EXPLORATION_ENABLED;
+            }
+            return clientState;
         }
     }
 
@@ -2680,7 +2628,7 @@
         private final Uri mTouchExplorationGrantedAccessibilityServicesUri = Settings.Secure
                 .getUriFor(Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES);
 
-        private final Uri mAccessibilityScriptInjectionUri = Settings.Secure
+        private final Uri mEnhancedWebAccessibilityUri = Settings.Secure
                 .getUriFor(Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION);
 
         public AccessibilityContentObserver(Handler handler) {
@@ -2699,7 +2647,7 @@
             contentResolver.registerContentObserver(
                     mTouchExplorationGrantedAccessibilityServicesUri,
                     false, this, UserHandle.USER_ALL);
-            contentResolver.registerContentObserver(mAccessibilityScriptInjectionUri,
+            contentResolver.registerContentObserver(mEnhancedWebAccessibilityUri,
                     false, this, UserHandle.USER_ALL);
         }
 
@@ -2708,58 +2656,61 @@
             if (mAccessibilityEnabledUri.equals(uri)) {
                 synchronized (mLock) {
                     // We will update when the automation service dies.
-                    if (mUiAutomationService == null) {
-                        UserState userState = getCurrentUserStateLocked();
-                        handleAccessibilityEnabledSettingChangedLocked(userState);
-                        performServiceManagementLocked(userState);
-                        scheduleUpdateInputFilter(userState);
-                        scheduleSendStateToClientsLocked(userState);
+                    UserState userState = getCurrentUserStateLocked();
+                    if (userState.mUiAutomationService == null) {
+                        if (readAccessibilityEnabledSettingLocked(userState)) {
+                            onUserStateChangedLocked(userState);
+                        }
                     }
                 }
             } else if (mTouchExplorationEnabledUri.equals(uri)) {
                 synchronized (mLock) {
                     // We will update when the automation service dies.
-                    if (mUiAutomationService == null) {
-                        UserState userState = getCurrentUserStateLocked();
-                        handleTouchExplorationEnabledSettingChangedLocked(userState);
-                        scheduleUpdateInputFilter(userState);
-                        scheduleSendStateToClientsLocked(userState);
+                    UserState userState = getCurrentUserStateLocked();
+                    if (userState.mUiAutomationService == null) {
+                        if (readTouchExplorationEnabledSettingLocked(userState)) {
+                            onUserStateChangedLocked(userState);
+                        }
                     }
                 }
             } else if (mDisplayMagnificationEnabledUri.equals(uri)) {
                 synchronized (mLock) {
                     // We will update when the automation service dies.
-                    if (mUiAutomationService == null) {
-                        UserState userState = getCurrentUserStateLocked();
-                        handleDisplayMagnificationEnabledSettingChangedLocked(userState);
-                        scheduleUpdateInputFilter(userState);
-                        scheduleSendStateToClientsLocked(userState);
+                    UserState userState = getCurrentUserStateLocked();
+                    if (userState.mUiAutomationService == null) {
+                        if (readDisplayMagnificationEnabledSettingLocked(userState)) {
+                            onUserStateChangedLocked(userState);
+                        }
                     }
                 }
             } else if (mEnabledAccessibilityServicesUri.equals(uri)) {
                 synchronized (mLock) {
                     // We will update when the automation service dies.
-                    if (mUiAutomationService == null) {
-                        UserState userState = getCurrentUserStateLocked();
-                        populateEnabledAccessibilityServicesLocked(userState);
-                        manageServicesLocked(userState);
+                    UserState userState = getCurrentUserStateLocked();
+                    if (userState.mUiAutomationService == null) {
+                        if (readEnabledAccessibilityServicesLocked(userState)) {
+                            onUserStateChangedLocked(userState);
+                        }
                     }
                 }
             } else if (mTouchExplorationGrantedAccessibilityServicesUri.equals(uri)) {
                 synchronized (mLock) {
                     // We will update when the automation service dies.
-                    if (mUiAutomationService == null) {
-                        UserState userState = getCurrentUserStateLocked();
-                        populateTouchExplorationGrantedAccessibilityServicesLocked(userState);
-                        handleTouchExplorationGrantedAccessibilityServicesChangedLocked(userState);
+                    UserState userState = getCurrentUserStateLocked();
+                    if (userState.mUiAutomationService == null) {
+                        if (readTouchExplorationGrantedAccessibilityServicesLocked(userState)) {
+                            onUserStateChangedLocked(userState);
+                        }
                     }
                 }
-            } else if (mAccessibilityScriptInjectionUri.equals(uri)) {
+            } else if (mEnhancedWebAccessibilityUri.equals(uri)) {
                 synchronized (mLock) {
                     // We will update when the automation service dies.
-                    if (mUiAutomationService == null) {
-                        UserState userState = getCurrentUserStateLocked();
-                        populatedEnhancedWebAccessibilityEnabledChangedLocked(userState);
+                    UserState userState = getCurrentUserStateLocked();
+                    if (userState.mUiAutomationService == null) {
+                        if (readEnhancedWebAccessibilityEnabledChangedLocked(userState)) {
+                            onUserStateChangedLocked(userState);
+                        }
                     }
                 }
             }
diff --git a/services/java/com/android/server/wm/DisplayMagnifier.java b/services/java/com/android/server/wm/DisplayMagnifier.java
index 6e876f6..61093ad 100644
--- a/services/java/com/android/server/wm/DisplayMagnifier.java
+++ b/services/java/com/android/server/wm/DisplayMagnifier.java
@@ -86,8 +86,8 @@
         mContext = windowManagerService.mContext;
         mWindowManagerService = windowManagerService;
         mCallbacks = callbacks;
-        mMagnifedViewport = new MagnifiedViewport();
         mHandler = new MyHandler(mWindowManagerService.mH.getLooper());
+        mMagnifedViewport = new MagnifiedViewport();
         mLongAnimationDuration = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_longAnimTime);
     }
@@ -273,6 +273,7 @@
                             mContext.getResources().getDisplayMetrics());
             mHalfBorderWidth = (int) (mBorderWidth + 0.5) / 2;
             mWindow = new ViewportWindow(mContext);
+            recomputeBoundsLocked();
         }
 
         public void updateMagnificationSpecLocked(MagnificationSpec spec) {