First pass on accessibility

-> issue 10801717
-> issue 11012432
-> issue 11012764

Change-Id: I9a687a39a358441afd57c0c46b57399ecbf23c36
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index e3e065e..a11c487 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -408,6 +408,7 @@
         mStats = new Stats(this);
 
         mAppWidgetManager = AppWidgetManager.getInstance(this);
+
         mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
         mAppWidgetHost.startListening();
 
@@ -421,6 +422,7 @@
                     Environment.getExternalStorageDirectory() + "/launcher");
         }
 
+
         checkForLocaleChange();
         setContentView(R.layout.launcher);
 
@@ -920,8 +922,8 @@
                 mWorkspace.getCustomContentCallbacks().onShow();
             }
         }
-
         mWorkspace.updateInteractionForState();
+        mWorkspace.onResume();
     }
 
     @Override
@@ -984,14 +986,9 @@
         public void setScrollY(int scrollY);
     }
 
-    // Add a fullscreen unpadded view to the workspace to the left all other screens.
-    public QSBScroller addToCustomContentPage(View customContent) {
-        return addToCustomContentPage(customContent, null);
-    }
-
     public QSBScroller addToCustomContentPage(View customContent,
-            CustomContentCallbacks callbacks) {
-        mWorkspace.addToCustomContentPage(customContent, callbacks);
+            CustomContentCallbacks callbacks, String description) {
+        mWorkspace.addToCustomContentPage(customContent, callbacks, description);
         return mQsbScroller;
     }
 
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index c8e34dd..96d8c19 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -32,6 +32,7 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.v4.view.accessibility.AccessibilityEventCompat;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -334,6 +335,8 @@
     }
 
     protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
         // Hook up the page indicator
         ViewGroup parent = (ViewGroup) getParent();
         if (mPageIndicator == null && mPageIndicatorViewId > -1) {
@@ -347,9 +350,19 @@
             }
 
             mPageIndicator.addMarkers(markers, mAllowPagedViewAnimations);
+            mPageIndicator.setOnClickListener(getPageIndicatorClickListener());
+            mPageIndicator.setContentDescription(getPageIndicatorDescription());
         }
     }
 
+    protected String getPageIndicatorDescription() {
+        return getCurrentPageDescription();
+    }
+
+    protected OnClickListener getPageIndicatorClickListener() {
+        return null;
+    }
+
     protected void onDetachedFromWindow() {
         // Unhook the page indicator
         mPageIndicator = null;
@@ -649,6 +662,28 @@
         }
     }
 
+    private void sendScrollAccessibilityEvent() {
+        AccessibilityManager am =
+                (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        if (am.isEnabled()) {
+            AccessibilityEvent ev =
+                    AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
+            ev.getText().add("");
+            ev.setItemCount(getChildCount());
+            ev.setFromIndex(mCurrentPage);
+            int action = AccessibilityNodeInfo.ACTION_SCROLL_FORWARD;
+
+            if (getNextPage() >= mCurrentPage) {
+                action = AccessibilityNodeInfo.ACTION_SCROLL_FORWARD;
+            } else {
+                action = AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD;
+            }
+
+            ev.setAction(action);
+            sendAccessibilityEventUnchecked(ev);
+        }
+    }
+
     // we moved this functionality to a helper function so SmoothPagedView can reuse it
     protected boolean computeScrollHelper() {
         if (mScroller.computeScrollOffset()) {
@@ -663,6 +698,8 @@
             invalidate();
             return true;
         } else if (mNextPage != INVALID_PAGE) {
+            sendScrollAccessibilityEvent();
+
             mCurrentPage = Math.max(0, Math.min(mNextPage, getPageCount() - 1));
             mNextPage = INVALID_PAGE;
             notifyPageSwitchListener();
@@ -680,14 +717,11 @@
             }
 
             onPostReorderingAnimationCompleted();
-            // Notify the user when the page changes
-            AccessibilityManager accessibilityManager = (AccessibilityManager)
+            AccessibilityManager am = (AccessibilityManager)
                     getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
-            if (accessibilityManager.isEnabled()) {
-                AccessibilityEvent ev =
-                    AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_SCROLLED);
-                ev.getText().add(getCurrentPageDescription());
-                sendAccessibilityEventUnchecked(ev);
+            if (am.isEnabled()) {
+                // Notify the user when the page changes
+                announceForAccessibility(getCurrentPageDescription());
             }
             return true;
         }
@@ -2133,6 +2167,8 @@
             focusedChild.clearFocus();
         }
 
+        sendScrollAccessibilityEvent();
+
         pageBeginMoving();
         awakenScrollBars(duration);
         if (immediate) {
@@ -2719,11 +2755,6 @@
     public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
         super.onInitializeAccessibilityEvent(event);
         event.setScrollable(true);
-        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
-            event.setFromIndex(mCurrentPage);
-            event.setToIndex(mCurrentPage);
-            event.setItemCount(getChildCount());
-        }
     }
 
     @Override
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 5a8472c..ef918e9 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -44,6 +44,7 @@
 import android.net.Uri;
 import android.os.IBinder;
 import android.os.Parcelable;
+import android.support.v4.view.ViewCompat;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
@@ -52,6 +53,9 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.widget.TextView;
@@ -113,6 +117,7 @@
     private int mDefaultPage;
 
     private ShortcutAndWidgetContainer mDragSourceInternal;
+    private static boolean sAccessibilityEnabled;
 
     // The screen id used for the empty screen always present to the right.
     private final static long EXTRA_EMPTY_SCREEN_ID = -201;
@@ -139,6 +144,7 @@
     CustomContentCallbacks mCustomContentCallbacks;
     boolean mCustomContentShowing;
     private float mLastCustomContentScrollProgress = -1f;
+    private String mCustomContentDescription = "";
 
     /**
      * The CellLayout that is currently being dragged over
@@ -318,11 +324,7 @@
 
         // Disable multitouch across the workspace/all apps/customize tray
         setMotionEventSplittingEnabled(true);
-
-        // Unless otherwise specified this view is important for accessibility.
-        if (getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
-            setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
-        }
+        setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
     }
 
     @Override
@@ -450,9 +452,7 @@
         CellLayout cl = ((CellLayout) child);
         cl.setOnInterceptTouchListener(this);
         cl.setClickable(true);
-        cl.setContentDescription(getContext().getString(
-                R.string.workspace_description_format, getChildCount()));
-
+        cl.setImportantForAccessibility(ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
         super.onChildViewAdded(parent, child);
     }
 
@@ -555,7 +555,8 @@
         setCurrentPage(getCurrentPage() - 1);
     }
 
-    public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks) {
+    public void addToCustomContentPage(View customContent, CustomContentCallbacks callbacks,
+            String description) {
         if (getPageIndexForScreenId(CUSTOM_CONTENT_SCREEN_ID) < 0) {
             throw new RuntimeException("Expected custom content screen to exist");
         }
@@ -571,6 +572,7 @@
             ((Insettable)customContent).setInsets(mInsets);
         }
         customScreen.addViewToCellLayout(customContent, 0, 0, lp, true);
+        mCustomContentDescription = description;
 
         mCustomContentCallbacks = callbacks;
     }
@@ -642,7 +644,6 @@
         return newId;
     }
 
-
     public CellLayout getScreenWithId(long screenId) {
         CellLayout layout = mWorkspaceScreens.get(screenId);
         return layout;
@@ -1039,6 +1040,9 @@
                 mLauncher.updateVoiceButtonProxyVisible(false);
             }
         }
+        if (getPageIndicator() != null) {
+            getPageIndicator().setContentDescription(getPageIndicatorDescription());
+        }
     }
 
     protected CustomContentCallbacks getCustomContentCallbacks() {
@@ -1412,6 +1416,22 @@
     }
 
     @Override
+    protected OnClickListener getPageIndicatorClickListener() {
+        AccessibilityManager am = (AccessibilityManager)
+                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        if (!am.isTouchExplorationEnabled()) {
+            return null;
+        }
+        OnClickListener listener = new OnClickListener() {
+            @Override
+            public void onClick(View arg0) {
+                enterOverviewMode();
+            }
+        };
+        return listener;
+    }
+
+    @Override
     protected void screenScrolled(int screenCenter) {
         final boolean isRtl = isLayoutRtl();
         super.screenScrolled(screenCenter);
@@ -1475,6 +1495,17 @@
         mWindowToken = null;
     }
 
+    protected void onResume() {
+        if (getPageIndicator() != null) {
+            // In case accessibility state has changed, we need to perform this on every
+            // attach to window
+            getPageIndicator().setOnClickListener(getPageIndicatorClickListener());
+        }
+        AccessibilityManager am = (AccessibilityManager)
+                getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        sAccessibilityEnabled = am.isEnabled();
+    }
+
     @Override
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         if (mFirstLayout && mCurrentPage >= 0 && mCurrentPage < getChildCount()) {
@@ -1852,6 +1883,14 @@
     private void setState(State state) {
         mState = state;
         updateInteractionForState();
+        updateAccessibilityFlags();
+    }
+
+    private void updateAccessibilityFlags() {
+        int accessible = mState == State.NORMAL ?
+                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES :
+                ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+        setImportantForAccessibility(accessible);
     }
 
     Animator getChangeStateAnimation(final State state, boolean animated, int delay, int snapPage) {
@@ -2036,8 +2075,11 @@
         }
 
         public static void updateVisibility(View view) {
-            if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != INVISIBLE) {
-                view.setVisibility(INVISIBLE);
+            // We want to avoid the extra layout pass by setting the views to GONE unless
+            // accessibility is on, in which case not setting them to GONE causes a glitch.
+            int invisibleState = sAccessibilityEnabled ? GONE : INVISIBLE;
+            if (view.getAlpha() < ALPHA_CUTOFF_THRESHOLD && view.getVisibility() != invisibleState) {
+                view.setVisibility(invisibleState);
             } else if (view.getAlpha() > ALPHA_CUTOFF_THRESHOLD
                     && view.getVisibility() != VISIBLE) {
                 view.setVisibility(VISIBLE);
@@ -4308,10 +4350,19 @@
     public void syncPageItems(int page, boolean immediate) {
     }
 
+    protected String getPageIndicatorDescription() {
+        String settings = getResources().getString(R.string.settings_button_text);
+        return getCurrentPageDescription() + ", " + settings;
+    }
+
     protected String getCurrentPageDescription() {
         int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+        int delta = numCustomPages();
+        if (hasCustomContent() && getNextPage() == 0) {
+            return mCustomContentDescription;
+        }
         return String.format(getContext().getString(R.string.workspace_scroll_format),
-                page + 1, getChildCount());
+                page + 1 - delta, getChildCount() - delta);
     }
 
     public void getLocationInDragLayer(int[] loc) {