Global gesture to toggle Accessibility system-wide.

1. This change adds a global gesture for enabling accessibility.
   To enable this gesture the user has to allow it from the
   accessibility settings or use the setup wizard to enable
   accessibility. When the global gesture is enabled the user
   can long press on power to bring the global actions dialog
   and then hold with two fingers for a few seconds to enable
   accessibility. The appropriate feedback is also provided.

2. The global gesture is writing directly into the settings for
   the current user if performed when the keyguard is not on. If
   the keygaurd is on and the current user has no accessibility
   enabled, the gesture will temporary enable accessibility
   for the current user, i.e. no settings are changed, to allow
   the blind user to log into his account. As soon as a user
   switch happens the new user settings are inherited. If no
   user change happens after temporary enabling accessibility
   the temporary changes will be undone when the keyguard goes
   away and the device will works as expected by the current user.

bug:6171929

3. The initialization code for the owner was not executed due
   to a redundant check, thus putting the accessibility layer in
   an inconsistent state which breaks pretty much everything.

bug:7240414

Change-Id: Ie7d7aba80f5867b7f88d5893b848b53fb02a7537
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index ffc48d8..550713d 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3300,7 +3300,7 @@
             "enabled_accessibility_services";
 
         /**
-         * List of the accessibility services to which the user has graned
+         * List of the accessibility services to which the user has granted
          * permission to put the device into touch exploration mode.
          *
          * @hide
@@ -3319,7 +3319,7 @@
          * <p>
          *   Note: The JavaScript based screen-reader is served by the
          *   Google infrastructure and enable users with disabilities to
-         *   efficiantly navigate in and explore web content.
+         *   efficiently navigate in and explore web content.
          * </p>
          * <p>
          *   This property represents a boolean value.
@@ -3331,7 +3331,7 @@
 
         /**
          * The URL for the injected JavaScript based screen-reader used
-         * for providing accessiblity of content in WebView.
+         * for providing accessibility of content in WebView.
          * <p>
          *   Note: The JavaScript based screen-reader is served by the
          *   Google infrastructure and enable users with disabilities to
@@ -4110,6 +4110,15 @@
         public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/global");
 
         /**
+         * Setting whether the global gesture for enabling accessibility is enabled.
+         * If this gesture is enabled the user will be able to perfrom it to enable
+         * the accessibility state without visiting the settings app.
+         * @hide
+         */
+        public static final String ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED =
+                "enable_accessibility_global_gesture_enabled";
+
+        /**
          * Whether Airplane Mode is on.
          */
         public static final String AIRPLANE_MODE_ON = "airplane_mode_on";
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 60238627..c3ef54c 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -20,6 +20,7 @@
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.accessibilityservice.IAccessibilityServiceClient;
+import android.content.ComponentName;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -53,4 +54,7 @@
         in AccessibilityServiceInfo info);
 
     void unregisterUiTestAutomationService(IAccessibilityServiceClient client);
+
+    void temporaryEnableAccessibilityStateUntilKeyguardRemoved(in ComponentName service,
+            boolean touchExplorationEnabled);
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index a8bee4d..8dbaa26 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1578,6 +1578,12 @@
         android:description="@string/permdesc_retrieve_window_info"
         android:protectionLevel="signature" />
 
+    <!-- @hide Allows an application to temporary enable accessibility on the device. -->
+    <permission android:name="android.permission.TEMPORARY_ENABLE_ACCESSIBILITY"
+        android:label="@string/permlab_temporary_enable_accessibility"
+        android:description="@string/permdesc_temporary_enable_accessibility"
+        android:protectionLevel="signature" />
+
     <!-- @hide Allows an application to magnify the content of a display. -->
     <permission android:name="android.permission.MAGNIFY_DISPLAY"
         android:label="@string/permlab_magnify_display"
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index e860dfe..63ed18e 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -285,4 +285,7 @@
     <!-- Space reserved at the bottom of secure views (pin/pattern/password/SIM pin/SIM puk) -->
     <dimen name="kg_secure_padding_height">46dp</dimen>
 
+    <!-- Touch slop for the global toggle accessibility gesture -->
+    <dimen name="accessibility_touch_slop">80dip</dimen>
+
 </resources>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index c90f4f2..02aa537 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -744,6 +744,13 @@
         the entire window content and examine all its text except passwords.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permlab_temporary_enable_accessibility">temporary enable accessibility</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+    <string name="permdesc_temporary_enable_accessibility">Allows an application to temporarily
+         enable accessibility on the device. Malicious apps may enable accessibility without
+         user consent.</string>
+
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_retrieve_window_info">retrieve window info</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permdesc_retrieve_window_info">Allows an application to retrieve
@@ -3903,4 +3910,13 @@
     </string>
 
 
+    <!-- Text spoken when the user is performing a gesture that will enable accessibility. [CHAR LIMIT=none] -->
+    <string name="continue_to_enable_accessibility">Continue touching the screen to enable accessibility.</string>
+    <!-- Text spoken when the user enabled accessibility. [CHAR LIMIT=none] -->
+    <string name="accessibility_enabled">Accessibility enabled.</string>
+    <!-- Text spoken when the user stops preforming a gesture that would enable accessibility. [CHAR LIMIT=none] -->
+    <string name="enable_accessibility_canceled">Enable accessibility canceled.</string>
+    <!-- Text spoken when the current user is switched if accessibility is enabled. [CHAR LIMIT=none] -->
+    <string name="user_switched">Switched to user <xliff:g id="name" example="Bob">%1$s</xliff:g>.</string>
+
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index d85e581..9a4136b 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -290,6 +290,7 @@
 
   <java-symbol type="color" name="tab_indicator_text_v4" />
 
+  <java-symbol type="dimen" name="accessibility_touch_slop" />
   <java-symbol type="dimen" name="config_prefDialogWidth" />
   <java-symbol type="dimen" name="config_viewConfigurationTouchSlop" />
   <java-symbol type="dimen" name="default_app_widget_padding_bottom" />
@@ -357,6 +358,7 @@
   <java-symbol type="string" name="abbrev_month_day_year" />
   <java-symbol type="string" name="abbrev_month_year" />
   <java-symbol type="string" name="accept" />
+  <java-symbol type="string" name="accessibility_enabled" />
   <java-symbol type="string" name="activity_chooser_view_see_all" />
   <java-symbol type="string" name="activitychooserview_choose_application" />
   <java-symbol type="string" name="alternate_eri_file" />
@@ -437,6 +439,7 @@
   <java-symbol type="string" name="contentServiceSync" />
   <java-symbol type="string" name="contentServiceSyncNotificationTitle" />
   <java-symbol type="string" name="contentServiceTooManyDeletesNotificationDesc" />
+  <java-symbol type="string" name="continue_to_enable_accessibility" />
   <java-symbol type="string" name="date1_date2" />
   <java-symbol type="string" name="date1_time1_date2_time2" />
   <java-symbol type="string" name="date_and_time" />
@@ -470,6 +473,7 @@
   <java-symbol type="string" name="emailTypeWork" />
   <java-symbol type="string" name="emergency_call_dialog_number_for_display" />
   <java-symbol type="string" name="emergency_calls_only" />
+  <java-symbol type="string" name="enable_accessibility_canceled" />
   <java-symbol type="string" name="eventTypeAnniversary" />
   <java-symbol type="string" name="eventTypeBirthday" />
   <java-symbol type="string" name="eventTypeCustom" />
@@ -778,6 +782,7 @@
   <java-symbol type="string" name="twelve_hour_time_format" />
   <java-symbol type="string" name="twenty_four_hour_time_format" />
   <java-symbol type="string" name="upload_file" />
+  <java-symbol type="string" name="user_switched" />
   <java-symbol type="string" name="volume_alarm" />
   <java-symbol type="string" name="volume_icon_description_bluetooth" />
   <java-symbol type="string" name="volume_icon_description_incall" />
diff --git a/policy/src/com/android/internal/policy/impl/EnableAccessibilityController.java b/policy/src/com/android/internal/policy/impl/EnableAccessibilityController.java
new file mode 100644
index 0000000..889463b
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/EnableAccessibilityController.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2012 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.android.internal.policy.impl;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.pm.ServiceInfo;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserManager;
+import android.provider.Settings;
+import android.speech.tts.TextToSpeech;
+import android.util.MathUtils;
+import android.view.IWindowManager;
+import android.view.MotionEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.IAccessibilityManager;
+
+import com.android.internal.R;
+
+import java.util.Iterator;
+import java.util.List;
+
+public class EnableAccessibilityController {
+
+    private static final int SPEAK_WARNING_DELAY_MILLIS = 2000;
+    private static final int ENABLE_ACCESSIBILITY_DELAY_MILLIS = 6000;
+
+    public static final int MESSAGE_SPEAK_WARNING = 1;
+    public static final int MESSAGE_SPEAK_ENABLE_CANCELED = 2;
+    public static final int MESSAGE_ENABLE_ACCESSIBILITY = 3;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case MESSAGE_SPEAK_WARNING: {
+                    String text = mContext.getString(R.string.continue_to_enable_accessibility);
+                    mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
+                } break;
+                case MESSAGE_SPEAK_ENABLE_CANCELED: {
+                    String text = mContext.getString(R.string.enable_accessibility_canceled);
+                    mTts.speak(text, TextToSpeech.QUEUE_FLUSH, null);
+                } break;
+                case MESSAGE_ENABLE_ACCESSIBILITY: {
+                    enableAccessibility();
+                    mTone.play();
+                    mTts.speak(mContext.getString(R.string.accessibility_enabled),
+                            TextToSpeech.QUEUE_FLUSH, null);
+                } break;
+            }
+        }
+    };
+
+    private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
+            ServiceManager.getService("window"));
+
+    private final IAccessibilityManager mAccessibilityManager = IAccessibilityManager
+            .Stub.asInterface(ServiceManager.getService("accessibility"));
+
+
+    private final Context mContext;
+    private final UserManager mUserManager;
+    private final TextToSpeech mTts;
+    private final Ringtone mTone;
+
+    private final float mTouchSlop;
+
+    private boolean mDestroyed;
+    private boolean mCanceled;
+
+    private float mFirstPointerDownX;
+    private float mFirstPointerDownY;
+    private float mSecondPointerDownX;
+    private float mSecondPointerDownY;
+
+    public EnableAccessibilityController(Context context) {
+        mContext = context;
+        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+        mTts = new TextToSpeech(context, new TextToSpeech.OnInitListener() {
+            @Override
+            public void onInit(int status) {
+                if (mDestroyed) {
+                    mTts.shutdown();
+                }
+            }
+        });
+        mTone = RingtoneManager.getRingtone(context, Settings.System.DEFAULT_NOTIFICATION_URI);
+        mTone.setStreamType(AudioManager.STREAM_MUSIC);
+        mTouchSlop = context.getResources().getDimensionPixelSize(
+                R.dimen.accessibility_touch_slop);
+    }
+
+    public static boolean canEnableAccessibilityViaGesture(Context context) {
+        AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(context);
+        // Accessibility is enabled and there is an enabled speaking
+        // accessibility service, then we have nothing to do.
+        if (accessibilityManager.isEnabled()
+                && !accessibilityManager.getEnabledAccessibilityServiceList(
+                        AccessibilityServiceInfo.FEEDBACK_SPOKEN).isEmpty()) {
+            return false;
+        }
+        // If the global gesture is enabled and there is a speaking service
+        // installed we are good to go, otherwise there is nothing to do.
+        return Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1
+                && !getInstalledSpeakingAccessibilityServices(context).isEmpty();
+    }
+
+    private static List<AccessibilityServiceInfo> getInstalledSpeakingAccessibilityServices(
+            Context context) {
+        List<AccessibilityServiceInfo> services = AccessibilityManager.getInstance(
+                context).getInstalledAccessibilityServiceList();
+        Iterator<AccessibilityServiceInfo> iterator = services.iterator();
+        while (iterator.hasNext()) {
+            AccessibilityServiceInfo service = iterator.next();
+            if ((service.feedbackType & AccessibilityServiceInfo.FEEDBACK_SPOKEN) == 0) {
+                iterator.remove();
+            }
+        }
+        return services;
+    }
+
+    public void onDestroy() {
+        mDestroyed = true;
+    }
+
+    public boolean onInterceptTouchEvent(MotionEvent event) {
+        if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN
+                && event.getPointerCount() == 2) {
+            mFirstPointerDownX = event.getX(0);
+            mFirstPointerDownY = event.getY(0);
+            mSecondPointerDownX = event.getX(1);
+            mSecondPointerDownY = event.getY(1);
+            mHandler.sendEmptyMessageDelayed(MESSAGE_SPEAK_WARNING,
+                    SPEAK_WARNING_DELAY_MILLIS);
+            mHandler.sendEmptyMessageDelayed(MESSAGE_ENABLE_ACCESSIBILITY,
+                   ENABLE_ACCESSIBILITY_DELAY_MILLIS);
+            return true;
+        }
+        return false;
+    }
+
+    public boolean onTouchEvent(MotionEvent event) {
+        final int pointerCount = event.getPointerCount();
+        final int action = event.getActionMasked();
+        if (mCanceled) {
+            if (action == MotionEvent.ACTION_UP) {
+                mCanceled = false;
+            }
+            return true;
+        }
+        switch (action) {
+            case MotionEvent.ACTION_POINTER_DOWN: {
+                if (pointerCount > 2) {
+                    cancel();
+                }
+            } break;
+            case MotionEvent.ACTION_MOVE: {
+                final float firstPointerMove = MathUtils.dist(event.getX(0),
+                        event.getY(0), mFirstPointerDownX, mFirstPointerDownY);
+                if (Math.abs(firstPointerMove) > mTouchSlop) {
+                    cancel();
+                }
+                final float secondPointerMove = MathUtils.dist(event.getX(1),
+                        event.getY(1), mSecondPointerDownX, mSecondPointerDownY);
+                if (Math.abs(secondPointerMove) > mTouchSlop) {
+                    cancel();
+                }
+            } break;
+            case MotionEvent.ACTION_POINTER_UP:
+            case MotionEvent.ACTION_CANCEL: {
+                cancel();
+            } break;
+        }
+        return true;
+    }
+
+    private void cancel() {
+        mCanceled = true;
+        if (mHandler.hasMessages(MESSAGE_SPEAK_WARNING)) {
+            mHandler.removeMessages(MESSAGE_SPEAK_WARNING);
+        } else if (mHandler.hasMessages(MESSAGE_ENABLE_ACCESSIBILITY)) {
+            mHandler.sendEmptyMessage(MESSAGE_SPEAK_ENABLE_CANCELED);
+        }
+        mHandler.removeMessages(MESSAGE_ENABLE_ACCESSIBILITY);
+    }
+
+    private void enableAccessibility() {
+        List<AccessibilityServiceInfo> services = getInstalledSpeakingAccessibilityServices(
+                mContext);
+        if (services.isEmpty()) {
+            return;
+        }
+        boolean keyguardLocked = false;
+        try {
+            keyguardLocked = mWindowManager.isKeyguardLocked();
+        } catch (RemoteException re) {
+            /* ignore */
+        }
+
+        final boolean hasMoreThanOneUser = mUserManager.getUsers().size() > 1;
+
+        AccessibilityServiceInfo service = services.get(0);
+        boolean enableTouchExploration = (service.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
+        // Try to find a service supporting explore by touch.
+        if (!enableTouchExploration) {
+            final int serviceCount = services.size();
+            for (int i = 1; i < serviceCount; i++) {
+                AccessibilityServiceInfo candidate = services.get(i);
+                if ((candidate.flags & AccessibilityServiceInfo
+                        .FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0) {
+                    enableTouchExploration = true;
+                    service = candidate;
+                    break;
+                }
+            }
+        }
+
+        ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
+        ComponentName componentName = new ComponentName(serviceInfo.packageName, serviceInfo.name);
+        if (!keyguardLocked || !hasMoreThanOneUser) {
+            final int userId = ActivityManager.getCurrentUser();
+            String enabledServiceString = componentName.flattenToString();
+            ContentResolver resolver = mContext.getContentResolver();
+            // Enable one speaking accessibility service.
+            Settings.Secure.putStringForUser(resolver,
+                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
+                    enabledServiceString, userId);
+            // Allow the services we just enabled to toggle touch exploration.
+            Settings.Secure.putStringForUser(resolver,
+                    Settings.Secure.TOUCH_EXPLORATION_GRANTED_ACCESSIBILITY_SERVICES,
+                    enabledServiceString, userId);
+            // Enable touch exploration.
+            if (enableTouchExploration) {
+                Settings.Secure.putIntForUser(resolver, Settings.Secure.TOUCH_EXPLORATION_ENABLED,
+                        1, userId);
+            }
+            // Enable accessibility script injection (AndroidVox) for web content.
+            Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION,
+                    1, userId);
+            // Turn on accessibility mode last.
+            Settings.Secure.putIntForUser(resolver, Settings.Secure.ACCESSIBILITY_ENABLED,
+                    1, userId);
+        } else if (keyguardLocked) {
+            try {
+                mAccessibilityManager.temporaryEnableAccessibilityStateUntilKeyguardRemoved(
+                        componentName, enableTouchExploration);
+            } catch (RemoteException re) {
+                /* ignore */
+            }
+        }
+    }
+}
diff --git a/policy/src/com/android/internal/policy/impl/GlobalActions.java b/policy/src/com/android/internal/policy/impl/GlobalActions.java
index d8e361f..0f9ad59 100644
--- a/policy/src/com/android/internal/policy/impl/GlobalActions.java
+++ b/policy/src/com/android/internal/policy/impl/GlobalActions.java
@@ -16,12 +16,15 @@
 
 package com.android.internal.policy.impl;
 
+import com.android.internal.app.AlertController;
+import com.android.internal.app.AlertController.AlertParams;
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.R;
 
 import android.app.ActivityManagerNative;
 import android.app.AlertDialog;
+import android.app.Dialog;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -32,11 +35,11 @@
 import android.graphics.drawable.Drawable;
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
+import android.os.Bundle;
 import android.os.Handler;
-import android.os.IBinder;
 import android.os.Message;
 import android.os.RemoteException;
-import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -46,17 +49,21 @@
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
 import android.util.Log;
-import android.view.IWindowManager;
+import android.util.TypedValue;
+import android.view.InputDevice;
+import android.view.KeyEvent;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.view.WindowManager;
-import android.view.WindowManagerGlobal;
 import android.view.WindowManagerPolicy.WindowManagerFuncs;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
 import android.widget.ImageView;
 import android.widget.ImageView.ScaleType;
+import android.widget.ListView;
 import android.widget.TextView;
 
 import java.util.ArrayList;
@@ -78,7 +85,7 @@
     private final AudioManager mAudioManager;
 
     private ArrayList<Action> mItems;
-    private AlertDialog mDialog;
+    private GlobalActionsDialog mDialog;
 
     private Action mSilentModeAction;
     private ToggleAction mAirplaneModeOn;
@@ -150,7 +157,7 @@
      * Create the global actions dialog.
      * @return A new dialog.
      */
-    private AlertDialog createDialog() {
+    private GlobalActionsDialog createDialog() {
         // Simple toggle style if there's no vibrator, otherwise use a tri-state
         if (!mHasVibrator) {
             mSilentModeAction = new SilentModeToggleAction();
@@ -319,12 +326,14 @@
 
         mAdapter = new MyAdapter();
 
-        final AlertDialog.Builder ab = new AlertDialog.Builder(mContext);
+        AlertParams params = new AlertParams(mContext);
+        params.mAdapter = mAdapter;
+        params.mOnClickListener = this;
+        params.mForceInverseBackground = true;
 
-        ab.setAdapter(mAdapter, this)
-                .setInverseBackgroundForced(true);
+        GlobalActionsDialog dialog = new GlobalActionsDialog(mContext, params);
+        dialog.setCanceledOnTouchOutside(false); // Handled by the custom class.
 
-        final AlertDialog dialog = ab.create();
         dialog.getListView().setItemsCanFocus(true);
         dialog.getListView().setLongClickable(true);
         dialog.getListView().setOnItemLongClickListener(
@@ -872,4 +881,121 @@
             mAirplaneState = on ? ToggleAction.State.On : ToggleAction.State.Off;
         }
     }
+
+    private static final class GlobalActionsDialog extends Dialog implements DialogInterface {
+        private final Context mContext;
+        private final int mWindowTouchSlop;
+        private final AlertController mAlert;
+
+        private EnableAccessibilityController mEnableAccessibilityController;
+
+        private boolean mIntercepted;
+        private boolean mCancelOnUp;
+
+        public GlobalActionsDialog(Context context, AlertParams params) {
+            super(context, getDialogTheme(context));
+            mContext = context;
+            mAlert = new AlertController(mContext, this, getWindow());
+            mWindowTouchSlop = ViewConfiguration.get(context).getScaledWindowTouchSlop();
+            params.apply(mAlert);
+        }
+
+        private static int getDialogTheme(Context context) {
+            TypedValue outValue = new TypedValue();
+            context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogTheme,
+                    outValue, true);
+            return outValue.resourceId;
+        }
+
+        @Override
+        protected void onStart() {
+            // If global accessibility gesture can be performed, we will take care
+            // of dismissing the dialog on touch outside. This is because the dialog
+            // is dismissed on the first down while the global gesture is a long press
+            // with two fingers anywhere on the screen.
+            if (EnableAccessibilityController.canEnableAccessibilityViaGesture(mContext)) {
+                mEnableAccessibilityController = new EnableAccessibilityController(mContext);
+                super.setCanceledOnTouchOutside(false);
+            } else {
+                mEnableAccessibilityController = null;
+                super.setCanceledOnTouchOutside(true);
+            }
+            super.onStart();
+        }
+
+        @Override
+        protected void onStop() {
+            if (mEnableAccessibilityController != null) {
+                mEnableAccessibilityController.onDestroy();
+            }
+            super.onStop();
+        }
+
+        @Override
+        public boolean dispatchTouchEvent(MotionEvent event) {
+            if (mEnableAccessibilityController != null) {
+                final int action = event.getActionMasked();
+                if (action == MotionEvent.ACTION_DOWN) {
+                    View decor = getWindow().getDecorView();
+                    final int eventX = (int) event.getX();
+                    final int eventY = (int) event.getY();
+                    if (eventX < -mWindowTouchSlop
+                            || eventY < -mWindowTouchSlop
+                            || eventX >= decor.getWidth() + mWindowTouchSlop
+                            || eventY >= decor.getHeight() + mWindowTouchSlop) {
+                        mCancelOnUp = true;
+                    }
+                }
+                try {
+                    if (!mIntercepted) {
+                        mIntercepted = mEnableAccessibilityController.onInterceptTouchEvent(event);
+                        if (mIntercepted) {
+                            final long now = SystemClock.uptimeMillis();
+                            event = MotionEvent.obtain(now, now,
+                                    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
+                            event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
+                            mCancelOnUp = true;
+                        }
+                    } else {
+                        return mEnableAccessibilityController.onTouchEvent(event);
+                    }
+                } finally {
+                    if (action == MotionEvent.ACTION_UP) {
+                        if (mCancelOnUp) {
+                            cancel();
+                        }
+                        mCancelOnUp = false;
+                        mIntercepted = false;
+                    }
+                }
+            }
+            return super.dispatchTouchEvent(event);
+        }
+
+        public ListView getListView() {
+            return mAlert.getListView();
+        }
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            mAlert.installContent();
+        }
+
+        @Override
+        public boolean onKeyDown(int keyCode, KeyEvent event) {
+            if (mAlert.onKeyDown(keyCode, event)) {
+                return true;
+            }
+            return super.onKeyDown(keyCode, event);
+        }
+
+        @Override
+        public boolean onKeyUp(int keyCode, KeyEvent event) {
+            if (mAlert.onKeyUp(keyCode, event)) {
+                return true;
+            }
+            return super.onKeyUp(keyCode, event);
+        }
+    }
 }
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 13ad285..e37075f 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -40,6 +40,8 @@
 import android.graphics.Rect;
 import android.media.AudioManager;
 import android.media.IAudioService;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
 import android.os.Bundle;
 import android.os.FactoryTest;
 import android.os.Handler;
@@ -747,7 +749,9 @@
                 break;
             case LONG_PRESS_POWER_GLOBAL_ACTIONS:
                 mPowerKeyHandled = true;
-                performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+                if (!performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false)) {
+                    performAuditoryFeedbackForAccessibilityIfNeed();
+                }
                 sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
                 showGlobalActionsDialog();
                 break;
@@ -4250,6 +4254,25 @@
         }
     }
 
+    private void performAuditoryFeedbackForAccessibilityIfNeed() {
+        if (!isGlobalAccessibilityGestureEnabled()) {
+            return;
+        }
+        AudioManager audioManager = (AudioManager) mContext.getSystemService(
+                Context.AUDIO_SERVICE);
+        if (audioManager.isSilentMode()) {
+            return;
+        }
+        Ringtone ringTone = RingtoneManager.getRingtone(mContext,
+                Settings.System.DEFAULT_NOTIFICATION_URI);
+        ringTone.setStreamType(AudioManager.STREAM_MUSIC);
+        ringTone.play();
+    }
+    private boolean isGlobalAccessibilityGestureEnabled() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, 0) == 1;
+    }
+
     public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always) {
         final boolean hapticsDisabled = Settings.System.getIntForUser(mContext.getContentResolver(),
                 Settings.System.HAPTIC_FEEDBACK_ENABLED, 0, UserHandle.USER_CURRENT) == 0;
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 25f98de..cae67e9 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -56,6 +56,7 @@
 import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.UserHandle;
+import android.os.UserManager;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.TextUtils.SimpleStringSplitter;
@@ -108,9 +109,16 @@
 
     private static final String LOG_TAG = "AccessibilityManagerService";
 
+    // TODO: This is arbitrary. When there is time implement this by watching
+    //       when that accessibility services are bound.
+    private static final int WAIT_FOR_USER_STATE_FULLY_INITIALIZED_MILLIS = 5000;
+
     private static final String FUNCTION_REGISTER_UI_TEST_AUTOMATION_SERVICE =
         "registerUiTestAutomationService";
 
+    private static final String TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED =
+            "temporaryEnableAccessibilityStateUntilKeyguardRemoved";
+
     private static final char COMPONENT_NAME_SEPARATOR = ':';
 
     private static final int OWN_PROCESS_ID = android.os.Process.myPid();
@@ -157,6 +165,9 @@
 
     private final SparseArray<UserState> mUserStates = new SparseArray<UserState>();
 
+    private final TempUserStateChangeMemento mTempStateChangeForCurrentUserMemento =
+            new TempUserStateChangeMemento();
+
     private int mCurrentUserId = UserHandle.USER_OWNER;
 
     private UserState getCurrentUserStateLocked() {
@@ -268,12 +279,13 @@
         // package changes
         monitor.register(mContext, null,  UserHandle.ALL, true);
 
-        // user change
-        IntentFilter userFilter = new IntentFilter();
-        userFilter.addAction(Intent.ACTION_USER_SWITCHED);
-        userFilter.addAction(Intent.ACTION_USER_REMOVED);
+        // user change and unlock
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_USER_SWITCHED);
+        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
+        intentFilter.addAction(Intent.ACTION_USER_PRESENT);
 
-        mContext.registerReceiver(new BroadcastReceiver() {
+        mContext.registerReceiverAsUser(new BroadcastReceiver() {
             @Override
             public void onReceive(Context context, Intent intent) {
                 String action = intent.getAction();
@@ -281,9 +293,11 @@
                     switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
                 } 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();
                 }
             }
-        }, userFilter);
+        }, UserHandle.ALL, intentFilter, null, null);
     }
 
     public int addClient(IAccessibilityManagerClient client, int userId) {
@@ -510,6 +524,37 @@
         }
     }
 
+    public void temporaryEnableAccessibilityStateUntilKeyguardRemoved(
+            ComponentName service, boolean touchExplorationEnabled) {
+        mSecurityPolicy.enforceCallingPermission(
+                Manifest.permission.TEMPORARY_ENABLE_ACCESSIBILITY,
+                TEMPORARY_ENABLE_ACCESSIBILITY_UNTIL_KEYGUARD_REMOVED);
+        try {
+            if (!mWindowManagerService.isKeyguardLocked()) {
+                return;
+            }
+        } catch (RemoteException re) {
+            return;
+        }
+        synchronized (mLock) {
+            UserState userState = getCurrentUserStateLocked();
+            // Stash the old state so we can restore it when the keyguard is gone.
+            mTempStateChangeForCurrentUserMemento.initialize(mCurrentUserId, getCurrentUserStateLocked());
+            // Set the temporary state.
+            userState.mIsAccessibilityEnabled = true;
+            userState.mIsTouchExplorationEnabled= touchExplorationEnabled;
+            userState.mIsDisplayMagnificationEnabled = false;
+            userState.mEnabledServices.clear();
+            userState.mEnabledServices.add(service);
+            userState.mTouchExplorationGrantedServices.clear();
+            userState.mTouchExplorationGrantedServices.add(service);
+            // Update the internal state.
+            performServiceManagementLocked(userState);
+            updateInputFilterLocked(userState);
+            scheduleSendStateToClientsLocked(userState);
+        }
+    }
+
     public void unregisterUiTestAutomationService(IAccessibilityServiceClient serviceClient) {
         synchronized (mLock) {
             // Automation service is not bound, so pretend it died to perform clean up.
@@ -600,9 +645,9 @@
 
     private void switchUser(int userId) {
         synchronized (mLock) {
-            if (userId == mCurrentUserId) {
-                return;
-            }
+            // 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);
@@ -620,6 +665,10 @@
             // Recreate the internal state for the new user.
             mMainHandler.obtainMessage(MainHandler.MSG_SEND_RECREATE_INTERNAL_STATE,
                     mCurrentUserId, 0).sendToTarget();
+
+            // Schedule announcement of the current user if needed.
+            mMainHandler.sendEmptyMessageDelayed(MainHandler.MSG_ANNOUNCE_NEW_USER_IF_NEEDED,
+                    WAIT_FOR_USER_STATE_FULLY_INITIALIZED_MILLIS);
         }
     }
 
@@ -629,6 +678,21 @@
         }
     }
 
+    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);
+                updateInputFilterLocked(userState);
+                scheduleSendStateToClientsLocked(userState);
+            }
+        }
+    }
+
     private Service getQueryBridge() {
         if (mQueryBridge == null) {
             AccessibilityServiceInfo info = new AccessibilityServiceInfo();
@@ -1076,6 +1140,7 @@
         handleDisplayMagnificationEnabledSettingChangedLocked(userState);
         handleAccessibilityEnabledSettingChangedLocked(userState);
 
+        performServiceManagementLocked(userState);
         updateInputFilterLocked(userState);
         scheduleSendStateToClientsLocked(userState);
     }
@@ -1084,6 +1149,9 @@
         userState.mIsAccessibilityEnabled = Settings.Secure.getIntForUser(
                mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_ENABLED, 0, userState.mUserId) == 1;
+    }
+
+    private void performServiceManagementLocked(UserState userState) {
         if (userState.mIsAccessibilityEnabled ) {
             manageServicesLocked(userState);
         } else {
@@ -1186,6 +1254,7 @@
         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 MainHandler(Looper looper) {
             super(looper);
@@ -1226,6 +1295,25 @@
                     final int eventType = msg.arg2;
                     mSecurityPolicy.updateActiveWindow(windowId, eventType);
                 } break;
+                case MSG_ANNOUNCE_NEW_USER_IF_NEEDED: {
+                    announceNewUserIfNeeded();
+                } break;
+            }
+        }
+
+        private void announceNewUserIfNeeded() {
+            synchronized (mLock) {
+                UserState userState = getCurrentUserStateLocked();
+                if (userState.mIsAccessibilityEnabled) {
+                    UserManager userManager = (UserManager) mContext.getSystemService(
+                            Context.USER_SERVICE);
+                    String message = mContext.getString(R.string.user_switched,
+                            userManager.getUserInfo(mCurrentUserId).name);
+                    AccessibilityEvent event = AccessibilityEvent.obtain(
+                            AccessibilityEvent.TYPE_ANNOUNCEMENT);
+                    event.getText().add(message);
+                    sendAccessibilityEvent(event, mCurrentUserId);
+                }
             }
         }
 
@@ -2229,6 +2317,46 @@
         }
     }
 
+    private class TempUserStateChangeMemento {
+        public int mUserId = UserHandle.USER_NULL;
+        public boolean mIsAccessibilityEnabled;
+        public boolean mIsTouchExplorationEnabled;
+        public boolean mIsDisplayMagnificationEnabled;
+        public final Set<ComponentName> mEnabledServices = new HashSet<ComponentName>();
+        public final Set<ComponentName> mTouchExplorationGrantedServices =
+                new HashSet<ComponentName>();
+
+        public void initialize(int userId, UserState userState) {
+            mUserId = userId;
+            mIsAccessibilityEnabled = userState.mIsAccessibilityEnabled;
+            mIsTouchExplorationEnabled = userState.mIsTouchExplorationEnabled;
+            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.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;
+            mIsDisplayMagnificationEnabled = false;
+            mEnabledServices.clear();
+            mTouchExplorationGrantedServices.clear();
+        }
+    }
+
     private final class AccessibilityContentObserver extends ContentObserver {
 
         private final Uri mAccessibilityEnabledUri = Settings.Secure.getUriFor(
@@ -2272,6 +2400,7 @@
                     if (mUiAutomationService == null) {
                         UserState userState = getCurrentUserStateLocked();
                         handleAccessibilityEnabledSettingChangedLocked(userState);
+                        performServiceManagementLocked(userState);
                         updateInputFilterLocked(userState);
                         scheduleSendStateToClientsLocked(userState);
                     }