Accessibility: Page re-ordering in overview mode

Change-Id: I5fc0ad326a63b6768cb1fae55ee6e05a9fc2b659
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index a4593ec..0739bab 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -559,7 +559,7 @@
     /**
      * Sets the current page.
      */
-    void setCurrentPage(int currentPage) {
+    public void setCurrentPage(int currentPage) {
         if (!mScroller.isFinished()) {
             abortScrollerAnimation(true);
         }
@@ -2535,7 +2535,7 @@
         }
     }
 
-    protected void onStartReordering() {
+    public void onStartReordering() {
         // Set the touch state to reordering (allows snapping to pages, dragging a child, etc.)
         mTouchState = TOUCH_STATE_REORDERING;
         mIsReordering = true;
@@ -2555,7 +2555,7 @@
         }
     }
 
-    protected void onEndReordering() {
+    public void onEndReordering() {
         mIsReordering = false;
     }
 
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index e7a41e0..f2fa59b 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -63,6 +63,7 @@
 import com.android.launcher3.LauncherAccessibilityDelegate.AccessibilityDragSource;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.UninstallDropTarget.UninstallSource;
+import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate;
 import com.android.launcher3.compat.UserHandleCompat;
 import com.android.launcher3.util.LongArrayMap;
 import com.android.launcher3.util.Thunk;
@@ -277,6 +278,8 @@
     // Handles workspace state transitions
     private WorkspaceStateTransitionAnimation mStateTransitionAnimation;
 
+    private AccessibilityDelegate mPagesAccessibilityDelegate;
+
     private final Runnable mBindPages = new Runnable() {
         @Override
         public void run() {
@@ -2000,14 +2003,14 @@
         range[1] = Math.max(0,  end);
     }
 
-    protected void onStartReordering() {
+    public void onStartReordering() {
         super.onStartReordering();
         showOutlines();
         // Reordering handles its own animations, disable the automatic ones.
         disableLayoutTransitions();
     }
 
-    protected void onEndReordering() {
+    public void onEndReordering() {
         super.onEndReordering();
 
         if (mLauncher.isWorkspaceLoading()) {
@@ -2068,11 +2071,45 @@
         return mState;
     }
 
-    private void updateAccessibilityFlags() {
-        int accessible = mState == State.NORMAL ?
-                IMPORTANT_FOR_ACCESSIBILITY_NO :
-                IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
-        setImportantForAccessibility(accessible);
+    public void updateAccessibilityFlags() {
+        if (Utilities.isLmpOrAbove()) {
+            int total = getPageCount();
+            for (int i = numCustomPages(); i < total; i++) {
+                updateAccessibilityFlags((CellLayout) getPageAt(i), i);
+            }
+            if (mState == State.NORMAL) {
+                setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+            } else {
+                setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+            }
+        } else {
+            int accessible = mState == State.NORMAL ?
+                    IMPORTANT_FOR_ACCESSIBILITY_NO :
+                        IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+            setImportantForAccessibility(accessible);
+        }
+    }
+
+    private void updateAccessibilityFlags(CellLayout page, int pageNo) {
+        if (mState == State.OVERVIEW) {
+            page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
+            page.getShortcutsAndWidgets().setImportantForAccessibility(
+                    IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
+            page.setContentDescription(getPageDescription(pageNo));
+
+            if (mPagesAccessibilityDelegate == null) {
+                mPagesAccessibilityDelegate = new OverviewScreenAccessibilityDelegate(this);
+            }
+            page.setAccessibilityDelegate(mPagesAccessibilityDelegate);
+        } else {
+            int accessible = mState == State.NORMAL ?
+                    IMPORTANT_FOR_ACCESSIBILITY_NO :
+                        IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
+            page.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
+            page.getShortcutsAndWidgets().setImportantForAccessibility(accessible);
+            page.setContentDescription(null);
+            page.setAccessibilityDelegate(null);
+        }
     }
 
     @Override
@@ -4460,11 +4497,15 @@
     }
 
     protected String getCurrentPageDescription() {
-        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
-        int delta = numCustomPages();
         if (hasCustomContent() && getNextPage() == 0) {
             return mCustomContentDescription;
         }
+        int page = (mNextPage != INVALID_PAGE) ? mNextPage : mCurrentPage;
+        return getPageDescription(page);
+    }
+
+    private String getPageDescription(int page) {
+        int delta = numCustomPages();
         return String.format(getContext().getString(R.string.workspace_scroll_format),
                 page + 1 - delta, getChildCount() - delta);
     }
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index a0cedeb..61a64e3 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -24,8 +24,11 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.accessibility.AccessibilityManager;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.DecelerateInterpolator;
+
 import com.android.launcher3.util.Thunk;
 
 import java.util.HashMap;
@@ -190,7 +193,7 @@
                               final HashMap<View, Integer> layerViews) {
         AccessibilityManager am = (AccessibilityManager)
                 mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE);
-        boolean accessibilityEnabled = am.isEnabled();
+        final boolean accessibilityEnabled = am.isEnabled();
 
         // Reinitialize animation arrays for the current workspace state
         reinitializeAnimationArrays();
@@ -301,7 +304,7 @@
         }
 
         final View searchBar = mLauncher.getOrCreateQsbBar();
-        final View overviewPanel = mLauncher.getOverviewPanel();
+        final ViewGroup overviewPanel = mLauncher.getOverviewPanel();
         final View hotseat = mLauncher.getHotseat();
         final View pageIndicator = mWorkspace.getPageIndicator();
         if (animated) {
@@ -424,6 +427,11 @@
                 @Override
                 public void onAnimationEnd(Animator animation) {
                     mStateAnimator = null;
+
+                    if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
+                        overviewPanel.getChildAt(0).performAccessibilityAction(
+                                AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+                    }
                 }
             });
         } else {
@@ -443,6 +451,11 @@
             mWorkspace.setScaleX(mNewScale);
             mWorkspace.setScaleY(mNewScale);
             mWorkspace.setTranslationY(finalWorkspaceTranslationY);
+
+            if (accessibilityEnabled && overviewPanel.getVisibility() == View.VISIBLE) {
+                overviewPanel.getChildAt(0).performAccessibilityAction(
+                        AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+            }
         }
 
         if (stateIsNormal) {
diff --git a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
new file mode 100644
index 0000000..d3f5230
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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.launcher3.accessibility;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import com.android.launcher3.R;
+import com.android.launcher3.Workspace;
+
+public class OverviewScreenAccessibilityDelegate extends AccessibilityDelegate {
+
+    private static final int MOVE_BACKWARD = R.id.action_move_screen_backwards;
+    private static final int MOVE_FORWARD = R.id.action_move_screen_forwards;
+
+    private final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
+    private final Workspace mWorkspace;
+
+    public OverviewScreenAccessibilityDelegate(Workspace workspace) {
+        mWorkspace = workspace;
+
+        Context context = mWorkspace.getContext();
+        boolean isRtl = mWorkspace.isLayoutRtl();
+        mActions.put(MOVE_BACKWARD, new AccessibilityAction(MOVE_BACKWARD,
+                context.getText(isRtl ? R.string.action_move_screen_right :
+                    R.string.action_move_screen_left)));
+        mActions.put(MOVE_FORWARD, new AccessibilityAction(MOVE_FORWARD,
+                context.getText(isRtl ? R.string.action_move_screen_left :
+                    R.string.action_move_screen_right)));
+    }
+
+    @Override
+    public boolean performAccessibilityAction(View host, int action, Bundle args) {
+        if (host != null) {
+            if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS ) {
+                int index = mWorkspace.indexOfChild(host);
+                mWorkspace.setCurrentPage(index);
+            } else if (action == MOVE_FORWARD) {
+                movePage(mWorkspace.indexOfChild(host) + 1, host);
+                return true;
+            } else if (action == MOVE_BACKWARD) {
+                movePage(mWorkspace.indexOfChild(host) - 1, host);
+                return true;
+            }
+        }
+
+        return super.performAccessibilityAction(host, action, args);
+    }
+
+    private void movePage(int finalIndex, View view) {
+        mWorkspace.onStartReordering();
+        mWorkspace.removeView(view);
+        mWorkspace.addView(view, finalIndex);
+        mWorkspace.onEndReordering();
+        mWorkspace.announceForAccessibility(mWorkspace.getContext().getText(R.string.screen_moved));
+
+        mWorkspace.updateAccessibilityFlags();
+        view.performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+    }
+
+    @Override
+    public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+        super.onInitializeAccessibilityNodeInfo(host, info);
+
+        int index = mWorkspace.indexOfChild(host);
+        if (index < mWorkspace.getChildCount() - 1) {
+            info.addAction(mActions.get(MOVE_FORWARD));
+        }
+        if (index > mWorkspace.numCustomPages()) {
+            info.addAction(mActions.get(MOVE_BACKWARD));
+        }
+    }
+}