Replacing accessibility service permissions with capability attributes.

Accessibility services can perform special operations such as retrieve
the screen content, enable explore by touch, etc. To ensure the user
is aware that the service will perform special operations we were using
permissions. However, the special operations cannot be performed unless
the service is really enabled by the user and it is at this point that
we want to notify the user about the service capabilities.

This change adds capability attributes to the accessibility service's
meta-data XML file. The service has to declare the capability and when
it is enabled we show the user the capabilities in the warining dialog.

bug:8633951

Change-Id: Id3442dc71dad018e606888afdc40834682fdb037
diff --git a/api/current.txt b/api/current.txt
index 803bb02..0430c3c6 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -37,8 +37,6 @@
     field public static final java.lang.String CALL_PHONE = "android.permission.CALL_PHONE";
     field public static final java.lang.String CALL_PRIVILEGED = "android.permission.CALL_PRIVILEGED";
     field public static final java.lang.String CAMERA = "android.permission.CAMERA";
-    field public static final java.lang.String CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = "android.permission.CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
-    field public static final java.lang.String CAN_REQUEST_TOUCH_EXPLORATION_MODE = "android.permission.CAN_REQUEST_TOUCH_EXPLORATION_MODE";
     field public static final java.lang.String CHANGE_COMPONENT_ENABLED_STATE = "android.permission.CHANGE_COMPONENT_ENABLED_STATE";
     field public static final java.lang.String CHANGE_CONFIGURATION = "android.permission.CHANGE_CONFIGURATION";
     field public static final java.lang.String CHANGE_NETWORK_STATE = "android.permission.CHANGE_NETWORK_STATE";
@@ -322,6 +320,9 @@
     field public static final int cacheColorHint = 16843009; // 0x1010101
     field public static final int calendarViewShown = 16843596; // 0x101034c
     field public static final int calendarViewStyle = 16843613; // 0x101035d
+    field public static final int canRequestEnhancedWebAccessibility = 16843735; // 0x10103d7
+    field public static final int canRequestFilterKeyEvents = 16843736; // 0x10103d8
+    field public static final int canRequestTouchExplorationMode = 16843734; // 0x10103d6
     field public static final int canRetrieveWindowContent = 16843653; // 0x1010385
     field public static final int candidatesTextStyleSpans = 16843312; // 0x1010230
     field public static final deprecated int capitalize = 16843113; // 0x1010169
@@ -2107,16 +2108,22 @@
 
   public class AccessibilityServiceInfo implements android.os.Parcelable {
     ctor public AccessibilityServiceInfo();
+    method public static java.lang.String capabilityToString(int);
     method public int describeContents();
     method public static java.lang.String feedbackTypeToString(int);
     method public static java.lang.String flagToString(int);
-    method public boolean getCanRetrieveWindowContent();
+    method public deprecated boolean getCanRetrieveWindowContent();
+    method public int getCapabilities();
     method public deprecated java.lang.String getDescription();
     method public java.lang.String getId();
     method public android.content.pm.ResolveInfo getResolveInfo();
     method public java.lang.String getSettingsActivityName();
     method public java.lang.String loadDescription(android.content.pm.PackageManager);
     method public void writeToParcel(android.os.Parcel, int);
+    field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
+    field public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 8; // 0x8
+    field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
+    field public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1; // 0x1
     field public static final android.os.Parcelable.Creator CREATOR;
     field public static final int DEFAULT = 1; // 0x1
     field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff
@@ -2129,6 +2136,7 @@
     field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
     field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
     field public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8
+    field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20
     field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
     field public int eventTypes;
     field public int feedbackType;
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index 31de98d..1e3d5be 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -308,6 +308,8 @@
      *     android:accessibilityFlags="flagDefault"
      *     android:settingsActivity="foo.bar.TestBackActivity"
      *     android:canRetrieveWindowContent="true"
+     *     android:canRequestTouchExplorationMode="true"
+     *     android:canRequestEnhancedWebAccessibility="true"
      *     . . .
      * /&gt;</pre>
      */
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index d82b9a3..40f45b7 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -29,6 +29,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.AttributeSet;
+import android.util.SparseArray;
 import android.util.TypedValue;
 import android.util.Xml;
 import android.view.View;
@@ -38,7 +39,12 @@
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
 
+import com.android.internal.R;
+
 import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * This class describes an {@link AccessibilityService}. The system notifies an
@@ -61,6 +67,49 @@
     private static final String TAG_ACCESSIBILITY_SERVICE = "accessibility-service";
 
     /**
+     * Capability: This accessibility service can retrieve the active window content.
+     */
+    public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 0x00000001;
+
+    /**
+     * Capability: This accessibility service can request touch exploration mode in which
+     * touched items are spoken aloud and the UI can be explored via gestures.
+     */
+    public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 0x00000002;
+
+    /**
+     * Capability: This accessibility service can request enhanced web accessibility
+     * enhancements. For example, installing scripts to make app content more accessible.
+     */
+    public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000004;
+
+    /**
+      * Capability: This accessibility service can request to filter the key event stream.
+     */
+    public static final int CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS = 0x00000008;
+
+    private static final SparseArray<CapabilityInfo> sAvailableCapabilityInfos =
+            new SparseArray<CapabilityInfo>();
+    static {
+        sAvailableCapabilityInfos.put(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
+                new CapabilityInfo(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
+                        R.string.capability_title_canRetrieveWindowContent,
+                        R.string.capability_desc_canRetrieveWindowContent));
+        sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
+                new CapabilityInfo(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
+                        R.string.capability_title_canRequestTouchExploration,
+                        R.string.capability_desc_canRequestTouchExploration));
+        sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY,
+                new CapabilityInfo(CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY,
+                        R.string.capability_title_canRequestEnhancedWebAccessibility,
+                        R.string.capability_desc_canRequestEnhancedWebAccessibility));
+        sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
+                new CapabilityInfo(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
+                        R.string.capability_title_canRequestFilterKeyEvents,
+                        R.string.capability_desc_canRequestFilterKeyEvents));
+    }
+
+    /**
      * Denotes spoken feedback.
      */
     public static final int FEEDBACK_SPOKEN = 0x0000001;
@@ -152,9 +201,11 @@
      * <p>
      * For accessibility services targeting API version higher than
      * {@link Build.VERSION_CODES#JELLY_BEAN_MR1} that want to set
-     * this flag have to request the
-     * {@link android.Manifest.permission#CAN_REQUEST_TOUCH_EXPLORATION_MODE}
-     * permission or the flag will be ignored.
+     * this flag have to declare this capability in their meta-data by setting
+     * the attribute {@link android.R.attr#canRequestTouchExplorationMode
+     * canRequestTouchExplorationMode} to true, otherwise this flag will
+     * be ignored. For how to declare the meta-data of a service refer to
+     * {@value AccessibilityService#SERVICE_META_DATA}.
      * </p>
      * <p>
      * Services targeting API version equal to or lower than
@@ -175,9 +226,11 @@
      * device will not have enhanced web accessibility enabled since there may be
      * another enabled service that requested it.
      * <p>
-     * Clients that want to set this flag have to request the
-     * {@link android.Manifest.permission#CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY}
-     * permission or the flag will be ignored.
+     * Services that want to set this flag have to declare this capability
+     * in their meta-data by setting the attribute {@link android.R.attr
+     * #canRequestEnhancedWebAccessibility canRequestEnhancedWebAccessibility} to
+     * true, otherwise this flag will be ignored. For how to declare the meta-data
+     * of a service refer to {@value AccessibilityService#SERVICE_META_DATA}.
      * </p>
      */
     public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000008;
@@ -192,6 +245,25 @@
     public static final int FLAG_REPORT_VIEW_IDS = 0x00000010;
 
     /**
+     * This flag requests from the system to filter key events. If this flag
+     * is set the accessibility service will receive the key events before
+     * applications allowing it implement global shortcuts. Setting this flag
+     * does not guarantee that this service will filter key events since only
+     * one service can do so at any given time. This avoids user confusion due
+     * to behavior change in case different key filtering services are enabled.
+     * If there is already another key filtering service enabled, this one will
+     * not receive key events.
+     * <p>
+     * Services that want to set this flag have to declare this capability
+     * in their meta-data by setting the attribute {@link android.R.attr
+     * #canRequestFilterKeyEvents canRequestFilterKeyEvents} to true,
+     * otherwise this flag will be ignored. For how to declare the meta-data
+     * of a service refer to {@value AccessibilityService#SERVICE_META_DATA}.
+     * </p>
+     */
+    public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 0x00000020;
+
+    /**
      * The event types an {@link AccessibilityService} is interested in.
      * <p>
      *   <strong>Can be dynamically set at runtime.</strong>
@@ -259,6 +331,9 @@
      * @see #DEFAULT
      * @see #FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
      * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+     * @see #FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY
+     * @see #FLAG_REQUEST_FILTER_KEY_EVENTS
+     * @see #FLAG_REPORT_VIEW_IDS
      */
     public int flags;
 
@@ -279,9 +354,9 @@
     private String mSettingsActivityName;
 
     /**
-     * Flag whether this accessibility service can retrieve window content.
+     * Bit mask with capabilities of this service.
      */
-    private boolean mCanRetrieveWindowContent;
+    private int mCapabilities;
 
     /**
      * Resource id of the description of the accessibility service.
@@ -360,9 +435,22 @@
                     com.android.internal.R.styleable.AccessibilityService_accessibilityFlags, 0);
             mSettingsActivityName = asAttributes.getString(
                     com.android.internal.R.styleable.AccessibilityService_settingsActivity);
-            mCanRetrieveWindowContent = asAttributes.getBoolean(
-                    com.android.internal.R.styleable.AccessibilityService_canRetrieveWindowContent,
-                    false);
+            if (asAttributes.getBoolean(com.android.internal.R.styleable
+                    .AccessibilityService_canRetrieveWindowContent, false)) {
+                mCapabilities |= CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT;
+            }
+            if (asAttributes.getBoolean(com.android.internal.R.styleable
+                    .AccessibilityService_canRequestTouchExplorationMode, false)) {
+                mCapabilities |= CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION;
+            }
+            if (asAttributes.getBoolean(com.android.internal.R.styleable
+                    .AccessibilityService_canRequestEnhancedWebAccessibility, false)) {
+                mCapabilities |= CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY;
+            }
+            if (asAttributes.getBoolean(com.android.internal.R.styleable
+                    .AccessibilityService_canRequestFilterKeyEvents, false)) {
+                mCapabilities |= CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS;
+            }
             TypedValue peekedValue = asAttributes.peekValue(
                     com.android.internal.R.styleable.AccessibilityService_description);
             if (peekedValue != null) {
@@ -446,9 +534,26 @@
      *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
      * </p>
      * @return True if window content can be retrieved.
+     *
+     * @deprecated Use {@link #getCapabilities()}.
      */
     public boolean getCanRetrieveWindowContent() {
-        return mCanRetrieveWindowContent;
+        return (mCapabilities & CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
+    }
+
+    /**
+     * Returns the bit mask of capabilities this accessibility service has such as
+     * being able to retrieve the active window content, etc.
+     *
+     * @return The capability bit mask.
+     *
+     * @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
+     * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+     * @see #CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY
+     * @see #CAPABILITY_FILTER_KEY_EVENTS
+     */
+    public int getCapabilities() {
+        return mCapabilities;
     }
 
     /**
@@ -502,7 +607,7 @@
         parcel.writeString(mId);
         parcel.writeParcelable(mResolveInfo, 0);
         parcel.writeString(mSettingsActivityName);
-        parcel.writeInt(mCanRetrieveWindowContent ? 1 : 0);
+        parcel.writeInt(mCapabilities);
         parcel.writeInt(mDescriptionResId);
         parcel.writeString(mNonLocalizedDescription);
     }
@@ -516,7 +621,7 @@
         mId = parcel.readString();
         mResolveInfo = parcel.readParcelable(null);
         mSettingsActivityName = parcel.readString();
-        mCanRetrieveWindowContent = (parcel.readInt() == 1);
+        mCapabilities = parcel.readInt();
         mDescriptionResId = parcel.readInt();
         mNonLocalizedDescription = parcel.readString();
     }
@@ -567,7 +672,7 @@
         stringBuilder.append(", ");
         stringBuilder.append("settingsActivityName: ").append(mSettingsActivityName);
         stringBuilder.append(", ");
-        stringBuilder.append("retrieveScreenContent: ").append(mCanRetrieveWindowContent);
+        appendCapabilities(stringBuilder, mCapabilities);
         return stringBuilder.toString();
     }
 
@@ -628,6 +733,20 @@
         stringBuilder.append("]");
     }
 
+    private static void appendCapabilities(StringBuilder stringBuilder, int capabilities) {
+        stringBuilder.append("capabilities:");
+        stringBuilder.append("[");
+        while (capabilities != 0) {
+            final int capabilityBit = (1 << Integer.numberOfTrailingZeros(capabilities));
+            stringBuilder.append(capabilityToString(capabilityBit));
+            capabilities &= ~capabilityBit;
+            if (capabilities != 0) {
+                stringBuilder.append(", ");
+            }
+        }
+        stringBuilder.append("]");
+    }
+
     /**
      * Returns the string representation of a feedback type. For example,
      * {@link #FEEDBACK_SPOKEN} is represented by the string FEEDBACK_SPOKEN.
@@ -699,12 +818,77 @@
                 return "FLAG_INCLUDE_NOT_IMPORTANT_VIEWS";
             case FLAG_REQUEST_TOUCH_EXPLORATION_MODE:
                 return "FLAG_REQUEST_TOUCH_EXPLORATION_MODE";
+            case FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
+                return "FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
+            case FLAG_REPORT_VIEW_IDS:
+                return "FLAG_REPORT_VIEW_IDS";
+            case FLAG_REQUEST_FILTER_KEY_EVENTS:
+                return "FLAG_REQUEST_FILTER_KEY_EVENTS";
             default:
                 return null;
         }
     }
 
     /**
+     * Returns the string representation of a capability. For example,
+     * {@link #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT} is represented
+     * by the string CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT.
+     *
+     * @param capability The capability.
+     * @return The string representation.
+     */
+    public static String capabilityToString(int capability) {
+        switch (capability) {
+            case CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT:
+                return "CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT";
+            case CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION:
+                return "CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION";
+            case CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY:
+                return "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
+            case CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS:
+                return "CAPABILITY_CAN_FILTER_KEY_EVENTS";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    /**
+     * @hide
+     * @return The list of {@link CapabilityInfo} objects.
+     */
+    public List<CapabilityInfo> getCapabilityInfos() {
+        if (mCapabilities == 0) {
+            return Collections.emptyList();
+        }
+        int capabilities = mCapabilities;
+        List<CapabilityInfo> capabilityInfos = new ArrayList<CapabilityInfo>();
+        while (capabilities != 0) {
+            final int capabilityBit = 1 << Integer.numberOfTrailingZeros(capabilities);
+            capabilities &= ~capabilityBit;
+            CapabilityInfo capabilityInfo = sAvailableCapabilityInfos.get(capabilityBit);
+            if (capabilityInfo != null) {
+                capabilityInfos.add(capabilityInfo);
+            }
+        }
+        return capabilityInfos;
+    }
+
+    /**
+     * @hide
+     */
+    public static final class CapabilityInfo {
+        public final int capability;
+        public final int titleResId;
+        public final int descResId;
+
+        public CapabilityInfo(int capability, int titleResId, int descResId) {
+            this.capability = capability;
+            this.titleResId = titleResId;
+            this.descResId = descResId;
+        }
+    }
+
+    /**
      * @see Parcelable.Creator
      */
     public static final Parcelable.Creator<AccessibilityServiceInfo> CREATOR =
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 96682807..cb14374 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -554,20 +554,6 @@
         android:description="@string/permgroupdesc_accessibilityFeatures"
         android:priority="380" />
 
-    <!-- Allows an accessibility service to request touch exploration mode. -->
-    <permission android:name="android.permission.CAN_REQUEST_TOUCH_EXPLORATION_MODE"
-        android:permissionGroup="android.permission-group.ACCESSIBILITY_FEATURES"
-        android:label="@string/permlab_canRequestTouchExplorationMode"
-        android:description="@string/permdesc_canRequestTouchExplorationMode"
-        android:protectionLevel="dangerous" />
-
-    <!-- Allows an accessibility service to request enhanced web accessibility. -->
-    <permission android:name="android.permission.CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY"
-        android:permissionGroup="android.permission-group.ACCESSIBILITY_FEATURES"
-        android:label="@string/permlab_canRequestEnahncedWebAccessibility"
-        android:description="@string/permdesc_canRequestEnahncedWebAccessibility"
-        android:protectionLevel="dangerous" />
-
     <!-- ======================================= -->
     <!-- Permissions for accessing location info -->
     <!-- ======================================= -->
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 8c7a374..60f3f32 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2521,13 +2521,43 @@
             <flag name="flagRequestTouchExplorationMode" value="0x00000004" />
             <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY} -->
             <flag name="flagRequestEnhancedWebAccessibility" value="0x00000008" />
+            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REPORT_VIEW_IDS} -->
+            <flag name="flagReportViewIds" value="0x00000010" />
+            <!-- Has flag {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_FILTER_KEY_EVENTS} -->
+            <flag name="flagRequestFilterKeyEvents" value="0x00000020" />
         </attr>
         <!-- Component name of an activity that allows the user to modify
              the settings for this service. This setting cannot be changed at runtime. -->
         <attr name="settingsActivity" />
-        <!-- Flag whether the accessibility service wants to be able to retrieve the
+        <!-- Attribute whether the accessibility service wants to be able to retrieve the
              active window content. This setting cannot be changed at runtime. -->
         <attr name="canRetrieveWindowContent" format="boolean" />
+        <!-- Attribute whether the accessibility service wants to be able to request touch
+             exploration mode in which touched items are spoken aloud and the UI can be
+             explored via gestures.
+             <p>
+             Required to allow setting the {@link android.accessibilityservice
+             #AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} flag.
+             </p>
+         -->
+        <attr name="canRequestTouchExplorationMode" format="boolean" />
+        <!-- Attribute whether the accessibility service wants to be able to request enhanced
+             web accessibility enhancements. For example, installing scripts to make app
+             content more accessible.
+             <p>
+             Required to allow setting the {@link android.accessibilityservice
+             #AccessibilityServiceInfo#FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY} flag.
+             </p>
+         -->
+        <attr name="canRequestEnhancedWebAccessibility" format="boolean" />
+        <!-- Attribute whether the accessibility service wants to be able to request to
+             filter key events.
+             <p>
+             Required to allow setting the {@link android.accessibilityservice
+             #AccessibilityServiceInfo#FLAG_REQUEST_FILTER_KEY_EVENTS} flag.
+             </p>
+         -->
+        <attr name="canRequestFilterKeyEvents" format="boolean" />
         <!-- Short description of the accessibility serivce purpose or behavior.-->
         <attr name="description" />
     </declare-styleable>
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 074d91f..f5db2ed 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2043,6 +2043,9 @@
   <public type="attr" name="childIndicatorEnd" />
   <public type="attr" name="restrictedAccountType" />
   <public type="attr" name="requiredAccountType" />
+  <public type="attr" name="canRequestTouchExplorationMode" />
+  <public type="attr" name="canRequestEnhancedWebAccessibility" />
+  <public type="attr" name="canRequestFilterKeyEvents" />
 
   <public type="style" name="Theme.NoTitleBar.Overscan" />
   <public type="style" name="Theme.Light.NoTitleBar.Overscan" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 3361ab7..db9602a 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -545,6 +545,31 @@
     <!-- Description of a category of application permissions, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permgroupdesc_accessibilityFeatures">Features that assistive technology can request.</string>
 
+    <!-- Title for the capability of an accessibility service to retrieve window content. -->
+    <string name="capability_title_canRetrieveWindowContent">Retrieve window content</string>
+    <!-- Description for the capability of an accessibility service to retrieve window content. -->
+    <string name="capability_desc_canRetrieveWindowContent">Inspect the content of a window you\'re
+        interacting with.</string>
+
+    <!-- Title for the capability of an accessibility service to request touch exploration. -->
+    <string name="capability_title_canRequestTouchExploration">Turn on Explore by Touch</string>
+    <!-- Description for the capability of an accessibility service to request touch exploration. -->
+    <string name="capability_desc_canRequestTouchExploration">Touched items will be spoken aloud
+        and the screen can be explored using gestures.</string>
+
+    <!-- Title for the capability of an accessibility service to request enhanced web accessibility. -->
+    <string name="capability_title_canRequestEnhancedWebAccessibility">Turn on enhanced web
+        accessibility</string>
+    <!-- Description for the capability of an accessibility service to request enhanced web accessibility. -->
+    <string name="capability_desc_canRequestEnhancedWebAccessibility">Scripts may be installed to
+        make app content more accessible.</string>
+
+    <!-- Title for the capability of an accessibility service to request to filter key events. -->
+    <string name="capability_title_canRequestFilterKeyEvents">Observe text you type</string>
+    <!-- Description for the capability of an accessibility service to request to filter key events. -->
+    <string name="capability_desc_canRequestFilterKeyEvents">Includes personal data such as credit
+        card numbers and passwords.</string>
+
     <!--  Permissions -->
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
@@ -945,20 +970,6 @@
         interface of an accessibility service. Should never be needed for normal apps.</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_canRequestTouchExplorationMode">request explore by touch</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_canRequestTouchExplorationMode">Allows the hoder to request an
-        interaction mode in which touched items are spoken aloud and the UI can be explored
-        via gestures.</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_canRequestEnahncedWebAccessibility">request enhanced web 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_canRequestEnahncedWebAccessibility">Allows the hoder to request
-        enabling of web accessibility enhancements. For example, installing scripts to make
-        app content more accessible.</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_bindTextService">bind to a text service</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_bindTextService">Allows the holder to bind to the top-level
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5e75390..fdf7c8f 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -438,6 +438,14 @@
   <java-symbol type="string" name="badPin" />
   <java-symbol type="string" name="badPuk" />
   <java-symbol type="string" name="byteShort" />
+  <java-symbol type="string" name="capability_desc_canRequestEnhancedWebAccessibility" />
+  <java-symbol type="string" name="capability_title_canRequestFilterKeyEvents" />
+  <java-symbol type="string" name="capability_desc_canRequestTouchExploration" />
+  <java-symbol type="string" name="capability_desc_canRetrieveWindowContent" />
+  <java-symbol type="string" name="capability_title_canRequestEnhancedWebAccessibility" />
+  <java-symbol type="string" name="capability_desc_canRequestFilterKeyEvents" />
+  <java-symbol type="string" name="capability_title_canRequestTouchExploration" />
+  <java-symbol type="string" name="capability_title_canRetrieveWindowContent" />
   <java-symbol type="string" name="cfTemplateForwarded" />
   <java-symbol type="string" name="cfTemplateForwardedTime" />
   <java-symbol type="string" name="cfTemplateNotForwarded" />
diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
index 7c6cd13..2f64908 100644
--- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -825,17 +825,21 @@
 
     private boolean notifyKeyEventLocked(KeyEvent event, int policyFlags, boolean isDefault) {
         // TODO: Now we are giving the key events to the last enabled
-        //       service that can handle them which is the last one
-        //       in our list since we write the last enabled as the
-        //       last record in the enabled services setting. Ideally,
-        //       the user should make the call which service handles
-        //       key events. However, only one service should handle
-        //       key events to avoid user frustration when different
-        //       behavior is observed from different combinations of
-        //       enabled accessibility services.
+        //       service that can handle them Ideally, the user should
+        //       make the call which service handles key events. However,
+        //       only one service should handle key events to avoid user
+        //       frustration when different behavior is observed from
+        //       different combinations of enabled accessibility services.
         UserState state = getCurrentUserStateLocked();
         for (int i = state.mBoundServices.size() - 1; i >= 0; i--) {
             Service service = state.mBoundServices.get(i);
+            // Key events are handled only by services that declared
+            // this capability and requested to filter key events.
+            if (!service.mRequestFilterKeyEvents ||
+                    (service.mAccessibilityServiceInfo.getCapabilities() & AccessibilityServiceInfo
+                            .CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS) == 0) {
+                continue;
+            }
             if (service.mIsDefault == isDefault) {
                 service.notifyKeyEvent(event, policyFlags);
                 return true;
@@ -1374,11 +1378,11 @@
                 return true;
             }
         } 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 (mContext.getPackageManager().checkPermission(
-                    android.Manifest.permission.CAN_REQUEST_TOUCH_EXPLORATION_MODE,
-                    service.mComponentName.getPackageName()) == PackageManager.PERMISSION_GRANTED) {
+            // Starting in JB-MR2 we request an accessibility service to declare
+            // certain capabilities in its meta-data to allow it to enable the
+            // corresponding features.
+            if (service.mIsAutomation || (service.mAccessibilityServiceInfo.getCapabilities()
+                    & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION) != 0) {
                 userState.mIsTouchExplorationEnabled = true;
                 Settings.Secure.putIntForUser(mContext.getContentResolver(),
                         Settings.Secure.TOUCH_EXPLORATION_ENABLED, 1, service.mUserId);
@@ -1407,9 +1411,8 @@
         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) {
+        if (service.mIsAutomation || (service.mAccessibilityServiceInfo.getCapabilities()
+               & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY) != 0) {
             userState.mIsEnhancedWebAccessibilityEnabled = true;
             Settings.Secure.putIntForUser(mContext.getContentResolver(),
                     Settings.Secure.ACCESSIBILITY_SCRIPT_INJECTION, 1, userState.mUserId);
@@ -1644,6 +1647,8 @@
 
         boolean mRequestEnhancedWebAccessibility;
 
+        boolean mRequestFilterKeyEvents;
+
         int mFetchFlags;
 
         long mNotificationTimeout;
@@ -1689,7 +1694,8 @@
             mAccessibilityServiceInfo = accessibilityServiceInfo;
             mIsAutomation = (sFakeAccessibilityServiceComponentName.equals(componentName));
             if (!mIsAutomation) {
-                mCanRetrieveScreenContent = accessibilityServiceInfo.getCanRetrieveWindowContent();
+                mCanRetrieveScreenContent = (accessibilityServiceInfo.getCapabilities()
+                        & AccessibilityServiceInfo.CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION) != 0;
                 mIntent = new Intent().setComponent(mComponentName);
                 mIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
                         com.android.internal.R.string.accessibility_binding_label);
@@ -1728,9 +1734,11 @@
 
             if (mResolveInfo != null) {
                 mRequestTouchExplorationMode = (info.flags
-                            & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
+                        & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
                 mRequestEnhancedWebAccessibility = (info.flags
-                           & AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY) != 0;
+                        & AccessibilityServiceInfo.FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY) != 0;
+                mRequestFilterKeyEvents = (info.flags
+                        & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS)  != 0;
             }
         }