Initial implementation of Nav Bar Accessibility Button

This allows an AccessibilityService to set a flag in its
AccessibilityServiceInfo that triggers the navigation bar to show an
Accessibility Button and observe callbacks when the button is clicked
or there are changes in the visibility of the navigation bar.

Test: Manual (Created a sample AccessibilityService) + CTS
Bug:29231271
Change-Id: I03d653d85bc37df28ed71d8bba94b7c75fe56e43
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 0000000..0615668
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 0000000..839e5ed
--- /dev/null
+++ b/packages/SystemUI/res/drawable-hdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 0000000..1480c865
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 0000000..66e11fb
--- /dev/null
+++ b/packages/SystemUI/res/drawable-mdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 0000000..d2fe0c3
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 0000000..5923269
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xhdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 0000000..d0196e5
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 0000000..d3a2b39
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxhdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button.png
new file mode 100644
index 0000000..726643c
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button.png
Binary files differ
diff --git a/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button_dark.png b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button_dark.png
new file mode 100644
index 0000000..31078f6
--- /dev/null
+++ b/packages/SystemUI/res/drawable-xxxhdpi/ic_sysbar_accessibility_button_dark.png
Binary files differ
diff --git a/packages/SystemUI/res/layout/menu_ime.xml b/packages/SystemUI/res/layout/menu_ime.xml
index 5e85ba0..6bd79a4 100644
--- a/packages/SystemUI/res/layout/menu_ime.xml
+++ b/packages/SystemUI/res/layout/menu_ime.xml
@@ -39,4 +39,13 @@
         android:contentDescription="@string/accessibility_ime_switch_button"
         android:scaleType="centerInside"
         />
+    <com.android.systemui.statusbar.policy.KeyButtonView
+        android:id="@+id/accessibility_button"
+        android:layout_width="@dimen/navigation_extra_key_width"
+        android:layout_height="match_parent"
+        android:layout_marginEnd="2dp"
+        android:visibility="invisible"
+        android:contentDescription="@string/accessibility_accessibility_button"
+        android:scaleType="centerInside"
+    />
 </FrameLayout>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 363b3e2..2cc2621 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -207,6 +207,8 @@
     <string name="accessibility_home">Home</string>
     <!-- Content description of the menu button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_menu">Menu</string>
+    <!-- Content description of the accessibility button in the navigation bar (not shown on the screen). [CHAR LIMIT=NONE] -->
+    <string name="accessibility_accessibility_button">Accessibility</string>
     <!-- Content description of the recents button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
     <string name="accessibility_recent">Overview</string>
     <!-- Content description of the search button for accessibility. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 62b536e..808cd21 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -23,6 +23,7 @@
 import static com.android.systemui.statusbar.phone.StatusBar.DEBUG_WINDOW_STATE;
 import static com.android.systemui.statusbar.phone.StatusBar.dumpBarTransitions;
 
+import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManagerNative;
@@ -79,6 +80,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -101,7 +103,7 @@
 
     private int mNavigationIconHints = 0;
     private int mNavigationBarMode;
-    protected AccessibilityManager mAccessibilityManager;
+    private AccessibilityManager mAccessibilityManager;
 
     private int mDisabledFlags1;
     private StatusBar mStatusBar;
@@ -132,6 +134,8 @@
         mDivider = SysUiServiceProvider.getComponent(getContext(), Divider.class);
         mWindowManager = getContext().getSystemService(WindowManager.class);
         mAccessibilityManager = getContext().getSystemService(AccessibilityManager.class);
+        mAccessibilityManager.addAccessibilityServicesStateChangeListener(
+                this::updateAccessibilityServicesState);
         if (savedInstanceState != null) {
             mDisabledFlags1 = savedInstanceState.getInt(EXTRA_DISABLE_STATE, 0);
         }
@@ -149,6 +153,8 @@
     public void onDestroy() {
         super.onDestroy();
         mCommandQueue.removeCallbacks(this);
+        mAccessibilityManager.removeAccessibilityServicesStateChangeListener(
+                this::updateAccessibilityServicesState);
         try {
             WindowManagerGlobal.getWindowManagerService()
                     .removeRotationWatcher(mRotationWatcher);
@@ -402,6 +408,10 @@
         ButtonDispatcher homeButton = mNavigationBarView.getHomeButton();
         homeButton.setOnTouchListener(this::onHomeTouch);
         homeButton.setOnLongClickListener(this::onHomeLongClick);
+
+        ButtonDispatcher accessibilityButton = mNavigationBarView.getAccessibilityButton();
+        accessibilityButton.setOnClickListener(this::onAccessibilityClick);
+        accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
     }
 
     private boolean onHomeTouch(View v, MotionEvent event) {
@@ -553,6 +563,34 @@
                 MetricsEvent.ACTION_WINDOW_UNDOCK_LONGPRESS);
     }
 
+    private void onAccessibilityClick(View v) {
+        mAccessibilityManager.notifyAccessibilityButtonClicked();
+    }
+
+    private boolean onAccessibilityLongClick(View v) {
+        // TODO(b/34720082): Target service selection via long click
+        android.widget.Toast.makeText(getContext(), "Service selection coming soon...",
+                android.widget.Toast.LENGTH_LONG).show();
+        return true;
+    }
+
+    private void updateAccessibilityServicesState() {
+        final List<AccessibilityServiceInfo> services =
+                mAccessibilityManager.getEnabledAccessibilityServiceList(
+                        AccessibilityServiceInfo.FEEDBACK_ALL_MASK);
+        int requestingServices = 0;
+        for (int i = services.size() - 1; i >= 0; --i) {
+            AccessibilityServiceInfo info = services.get(i);
+            if ((info.flags & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0) {
+                requestingServices++;
+            }
+        }
+
+        final boolean showAccessibilityButton = requestingServices >= 1;
+        final boolean targetSelection = requestingServices >= 2;
+        mNavigationBarView.setAccessibilityButtonState(showAccessibilityButton, targetSelection);
+    }
+
     // ----- Methods that StatusBar talks to (should be minimized) -----
 
     public void setLightBarController(LightBarController lightBarController) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index dced747..5d13289 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -78,6 +78,8 @@
     private int mCurrentRotation = -1;
 
     boolean mShowMenu;
+    boolean mShowAccessibilityButton;
+    boolean mLongClickableAccessibilityButton;
     int mDisabledFlags = 0;
     int mNavigationIconHints = 0;
 
@@ -89,6 +91,7 @@
     private KeyButtonDrawable mDockedIcon;
     private KeyButtonDrawable mImeIcon;
     private KeyButtonDrawable mMenuIcon;
+    private KeyButtonDrawable mAccessibilityIcon;
 
     private GestureHelper mGestureHelper;
     private DeadZone mDeadZone;
@@ -202,6 +205,9 @@
         mVertical = false;
         mShowMenu = false;
 
+        mShowAccessibilityButton = false;
+        mLongClickableAccessibilityButton = false;
+
         mConfiguration = new Configuration();
         mConfiguration.updateFrom(context.getResources().getConfiguration());
         updateIcons(context, Configuration.EMPTY, mConfiguration);
@@ -213,6 +219,8 @@
         mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
         mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu));
         mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher));
+        mButtonDispatchers.put(R.id.accessibility_button,
+                new ButtonDispatcher(R.id.accessibility_button));
     }
 
     public BarTransitions getBarTransitions() {
@@ -287,6 +295,10 @@
         return mButtonDispatchers.get(R.id.ime_switcher);
     }
 
+    public ButtonDispatcher getAccessibilityButton() {
+        return mButtonDispatchers.get(R.id.accessibility_button);
+    }
+
     public SparseArray<ButtonDispatcher> getButtonDispatchers() {
         return mButtonDispatchers;
     }
@@ -320,6 +332,8 @@
             mRecentIcon = getDrawable(ctx,
                     R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark);
             mMenuIcon = getDrawable(ctx, R.drawable.ic_sysbar_menu, R.drawable.ic_sysbar_menu_dark);
+            mAccessibilityIcon = getDrawable(ctx, R.drawable.ic_sysbar_accessibility_button,
+                    R.drawable.ic_sysbar_accessibility_button_dark);
 
             Context darkContext = new ContextThemeWrapper(ctx, R.style.DualToneDarkTheme);
             Context lightContext = new ContextThemeWrapper(ctx, R.style.DualToneLightTheme);
@@ -411,6 +425,9 @@
         setMenuVisibility(mShowMenu, true);
         getMenuButton().setImageDrawable(mMenuIcon);
 
+        setAccessibilityButtonState(mShowAccessibilityButton, mLongClickableAccessibilityButton);
+        getAccessibilityButton().setImageDrawable(mAccessibilityIcon);
+
         setDisabledFlags(mDisabledFlags, true);
 
         mBarTransitions.reapplyDarkIntensity();
@@ -517,13 +534,25 @@
 
         mShowMenu = show;
 
-        // Only show Menu if IME switcher not shown.
-        final boolean shouldShow = mShowMenu &&
+        // Only show Menu if IME switcher and Accessibility button not shown.
+        final boolean shouldShow = mShowMenu && !mShowAccessibilityButton &&
                 ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0);
 
         getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE);
     }
 
+    public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) {
+        mShowAccessibilityButton = visible;
+        mLongClickableAccessibilityButton = longClickable;
+        if (visible) {
+            // Accessibility button overrides Menu button.
+            setMenuVisibility(false, true);
+        }
+
+        getAccessibilityButton().setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
+        getAccessibilityButton().setLongClickable(longClickable);
+    }
+
     @Override
     public void onFinishInflate() {
         mNavigationInflaterView = (NavigationBarInflaterView) findViewById(
@@ -814,6 +843,7 @@
         dumpButton(pw, "home", getHomeButton());
         dumpButton(pw, "rcnt", getRecentsButton());
         dumpButton(pw, "menu", getMenuButton());
+        dumpButton(pw, "a11y", getAccessibilityButton());
 
         pw.println("    }");
     }