OSDN Git Service

Enabling accessibility drag and drop in folder
authorSunny Goyal <sunnygoyal@google.com>
Fri, 24 Apr 2015 18:44:51 +0000 (11:44 -0700)
committerSunny Goyal <sunnygoyal@google.com>
Tue, 28 Apr 2015 23:06:46 +0000 (16:06 -0700)
> Moving DragAndDropAccessibilityDelegate to a separate class
> Using getFocusedVirtualView() instead of using DownX and downY
> Updating various accessibility strings

Bug: 19776741

Change-Id: I85c2551d4d6172c30702e68f41b114bb999655b6

res/values/strings.xml
src/com/android/launcher3/ButtonDropTarget.java
src/com/android/launcher3/CellLayout.java
src/com/android/launcher3/DragController.java
src/com/android/launcher3/DropTarget.java
src/com/android/launcher3/Folder.java
src/com/android/launcher3/LauncherAccessibilityDelegate.java
src/com/android/launcher3/Workspace.java
src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java [new file with mode: 0644]
src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java [new file with mode: 0644]
src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java [new file with mode: 0644]

index a68f53a..3b94eb3 100644 (file)
@@ -307,10 +307,13 @@ s -->
 
 <!-- Strings for accessibility actions -->
     <!-- Accessibility action to add an app to workspace. [CHAR_LIMIT=30] [DO NOT TRANSLATE] -->
-    <string name="action_add_to_workspace">Add to workspace</string>
+    <string name="action_add_to_workspace">Add to home screen</string>
+
+    <!-- Accessibility action to move item to the current location. [CHAR_LIMIT=30] [DO NOT TRANSLATE] -->
+    <string name="action_move_here">Move here</string>
 
     <!-- Accessibility confirmation for item added to workspace [DO NOT TRANSLATE] -->
-    <string name="item_added_to_workspace">Item added to workspace</string>
+    <string name="item_added_to_workspace">Item added to home screen</string>
 
     <!-- Accessibility confirmation for item removed [DO NOT TRANSLATE] -->
     <string name="item_removed">Item removed</string>
@@ -319,7 +322,13 @@ s -->
     <string name="action_move">Move Item</string>
 
     <!-- Accessibility description to move item to empty cell. [DO NOT TRANSLATE] -->
-    <string name="move_to_empty_cell">Move to empty cell <xliff:g id="number" example="1">%1$s</xliff:g>, <xliff:g id="number" example="1">%2$s</xliff:g></string>
+    <string name="move_to_empty_cell">Move to row <xliff:g id="number" example="1">%1$s</xliff:g> column <xliff:g id="number" example="1">%2$s</xliff:g></string>
+
+    <!-- Accessibility description to move item inside a folder. [DO NOT TRANSLATE] -->
+    <string name="move_to_position">Move to position <xliff:g id="number" example="1">%1$s</xliff:g></string>
+
+    <!-- Accessibility description to move item to the hotseat. [DO NOT TRANSLATE] -->
+    <string name="move_to_hotseat_position">Move to favorites position <xliff:g id="number" example="1">%1$s</xliff:g></string>
 
     <!-- Accessibility confirmation for item move [DO NOT TRANSLATE]-->
     <string name="item_moved">Item moved</string>
@@ -327,6 +336,9 @@ s -->
     <!-- Accessibility description to move item into an existing folder. [DO NOT TRANSLATE]-->
     <string name="add_to_folder">Add to folder: <xliff:g id="name" example="Games">%1$s</xliff:g></string>
 
+    <!-- Accessibility description to move item into an existing folder containing an app. [DO NOT TRANSLATE]-->
+    <string name="add_to_folder_with_app">Add to folder with <xliff:g id="name" example="Messenger">%1$s</xliff:g></string>
+
     <!-- Accessibility confirmation for item added to folder [DO NOT TRANSLATE] -->
     <string name="added_to_folder">Item added to folder</string>
 
index fb49df5..daa07d0 100644 (file)
@@ -203,6 +203,9 @@ public abstract class ButtonDropTarget extends TextView
                 DragLayer.ANIMATION_END_DISAPPEAR, null);
     }
 
+    @Override
+    public void prepareAccessibilityDrop() { }
+
     @Thunk abstract void completeDrop(DragObject d);
 
     @Override
index 85653be..f5d2f7d 100644 (file)
@@ -35,11 +35,8 @@ import android.graphics.Rect;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.Parcelable;
 import android.support.v4.view.ViewCompat;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v4.widget.ExploreByTouchHelper;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.SparseArray;
@@ -51,7 +48,9 @@ import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.DecelerateInterpolator;
 
 import com.android.launcher3.FolderIcon.FolderRingAnimator;
-import com.android.launcher3.LauncherAccessibilityDelegate.DragType;
+import com.android.launcher3.accessibility.DragAndDropAccessibilityDelegate;
+import com.android.launcher3.accessibility.FolderAccessibilityHelper;
+import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper;
 import com.android.launcher3.util.Thunk;
 import com.android.launcher3.widget.PendingAddWidgetInfo;
 
@@ -60,10 +59,12 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Stack;
 
 public class CellLayout extends ViewGroup {
+    public static final int WORKSPACE_ACCESSIBILITY_DRAG = 2;
+    public static final int FOLDER_ACCESSIBILITY_DRAG = 1;
+
     static final String TAG = "CellLayout";
 
     private Launcher mLauncher;
@@ -178,12 +179,8 @@ public class CellLayout extends ViewGroup {
     private final static Paint sPaint = new Paint();
 
     // Related to accessible drag and drop
-    DragAndDropAccessibilityDelegate mTouchHelper = new DragAndDropAccessibilityDelegate(this);
+    private DragAndDropAccessibilityDelegate mTouchHelper;
     private boolean mUseTouchHelper = false;
-    OnClickListener mOldClickListener = null;
-    OnClickListener mOldWorkspaceListener = null;
-    @Thunk int mDownX = 0;
-    @Thunk int mDownY = 0;
 
     public CellLayout(Context context) {
         this(context, null);
@@ -311,14 +308,22 @@ public class CellLayout extends ViewGroup {
     }
 
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
-    public void enableAccessibleDrag(boolean enable) {
+    public void enableAccessibleDrag(boolean enable, int dragType) {
         mUseTouchHelper = enable;
+        Log.e("HIGHRES", getParent() + "  " + enable + "  " + dragType, new Exception());
         if (!enable) {
             ViewCompat.setAccessibilityDelegate(this, null);
             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
             getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
             setOnClickListener(mLauncher);
         } else {
+            if (dragType == WORKSPACE_ACCESSIBILITY_DRAG &&
+                    !(mTouchHelper instanceof WorkspaceAccessibilityHelper)) {
+                mTouchHelper = new WorkspaceAccessibilityHelper(this);
+            } else if (dragType == FOLDER_ACCESSIBILITY_DRAG &&
+                    !(mTouchHelper instanceof FolderAccessibilityHelper)) {
+                mTouchHelper = new FolderAccessibilityHelper(this);
+            }
             ViewCompat.setAccessibilityDelegate(this, mTouchHelper);
             setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
             getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
@@ -342,15 +347,6 @@ public class CellLayout extends ViewGroup {
     }
 
     @Override
-    public boolean dispatchTouchEvent(MotionEvent event) {
-        if (event.getAction() == MotionEvent.ACTION_DOWN) {
-            mDownX = (int) event.getX();
-            mDownY = (int) event.getY();
-        }
-        return super.dispatchTouchEvent(event);
-    }
-
-    @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (mUseTouchHelper ||
                 (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev))) {
@@ -359,252 +355,6 @@ public class CellLayout extends ViewGroup {
         return false;
     }
 
-    class DragAndDropAccessibilityDelegate extends ExploreByTouchHelper implements OnClickListener {
-        private final Rect mTempRect = new Rect();
-
-        public DragAndDropAccessibilityDelegate(View forView) {
-            super(forView);
-        }
-
-        private int getViewIdAt(float x, float y) {
-            if (x < 0 || y < 0 || x > getMeasuredWidth() || y > getMeasuredHeight()) {
-                return ExploreByTouchHelper.INVALID_ID;
-            }
-
-            // Map coords to cell
-            int cellX = (int) Math.floor(x / (mCellWidth + mWidthGap));
-            int cellY = (int) Math.floor(y / (mCellHeight + mHeightGap));
-
-            // Map cell to id
-            int id = cellX * mCountY + cellY;
-            return id;
-        }
-
-        @Override
-        protected int getVirtualViewAt(float x, float y) {
-            return nearestDropLocation(getViewIdAt(x, y));
-        }
-
-        protected int nearestDropLocation(int id) {
-            int count = mCountX * mCountY;
-            for (int delta = 0; delta < count; delta++) {
-                if (id + delta <= (count - 1)) {
-                    int target = intersectsValidDropTarget(id + delta);
-                    if (target >= 0) {
-                        return target;
-                    }
-                } else if (id - delta >= 0) {
-                    int target = intersectsValidDropTarget(id - delta);
-                    if (target >= 0) {
-                        return target;
-                    }
-                }
-            }
-            return ExploreByTouchHelper.INVALID_ID;
-        }
-
-        /**
-         * Find the virtual view id corresponding to the top left corner of any drop region by which
-         * the passed id is contained. For an icon, this is simply
-         *
-         * @param id the id we're interested examining (ie. does it fit there?)
-         * @return the view id of the top left corner of a valid drop region or -1 if there is no
-         *         such valid region. For the icon, this can just be -1 or id.
-         */
-        protected int intersectsValidDropTarget(int id) {
-            LauncherAccessibilityDelegate delegate =
-                    LauncherAppState.getInstance().getAccessibilityDelegate();
-            if (delegate == null) {
-                return -1;
-            }
-
-            int y = id % mCountY;
-            int x = id / mCountY;
-            LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
-
-            if (dragInfo.dragType == DragType.WIDGET) {
-                // For a widget, every cell must be vacant. In addition, we will return any valid
-                // drop target by which the passed id is contained.
-                boolean fits = false;
-
-                // These represent the amount that we can back off if we hit a problem. They
-                // get consumed as we move up and to the right, trying new regions.
-                int spanX = dragInfo.info.spanX;
-                int spanY = dragInfo.info.spanY;
-
-                for (int m = 0; m < spanX; m++) {
-                    for (int n = 0; n < spanY; n++) {
-
-                        fits = true;
-                        int x0 = x - m;
-                        int y0 = y - n;
-
-                        if (x0 < 0 || y0 < 0) continue;
-
-                        for (int i = x0; i < x0 + spanX; i++) {
-                            if (!fits) break;
-                            for (int j = y0; j < y0 + spanY; j++) {
-                                if (i >= mCountX || j >= mCountY || mOccupied[i][j]) {
-                                    fits = false;
-                                    break;
-                                }
-                            }
-                        }
-                        if (fits) {
-                            return x0 * mCountY + y0;
-                        }
-                    }
-                }
-                return -1;
-            } else {
-                // For an icon, we simply check the view directly below
-                View child = getChildAt(x, y);
-                if (child == null || child == dragInfo.item) {
-                    // Empty cell. Good for an icon or folder.
-                    return id;
-                } else if (dragInfo.dragType != DragType.FOLDER) {
-                    // For icons, we can consider cells that have another icon or a folder.
-                    ItemInfo info = (ItemInfo) child.getTag();
-                    if (info instanceof AppInfo || info instanceof FolderInfo ||
-                            info instanceof ShortcutInfo) {
-                        return id;
-                    }
-                }
-                return -1;
-            }
-        }
-
-        @Override
-        protected void getVisibleVirtualViews(List<Integer> virtualViews) {
-            // We create a virtual view for each cell of the grid
-            // The cell ids correspond to cells in reading order.
-            int nCells = mCountX * mCountY;
-
-            for (int i = 0; i < nCells; i++) {
-                if (intersectsValidDropTarget(i) >= 0) {
-                    virtualViews.add(i);
-                }
-            }
-        }
-
-        @Override
-        protected boolean onPerformActionForVirtualView(int viewId, int action, Bundle args) {
-            LauncherAccessibilityDelegate delegate =
-                    LauncherAppState.getInstance().getAccessibilityDelegate();
-            if (delegate == null) {
-                return false;
-            }
-
-            if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
-                String confirmation = getConfirmationForIconDrop(viewId);
-                delegate.handleAccessibleDrop(CellLayout.this, getItemBounds(viewId), confirmation);
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public void onClick(View arg0) {
-            LauncherAccessibilityDelegate delegate =
-                    LauncherAppState.getInstance().getAccessibilityDelegate();
-            if (delegate == null) {
-                return;
-            }
-
-            int viewId = getViewIdAt(mDownX, mDownY);
-
-            String confirmation = getConfirmationForIconDrop(viewId);
-            delegate.handleAccessibleDrop(CellLayout.this, getItemBounds(viewId), confirmation);
-        }
-
-        @Override
-        protected void onPopulateEventForVirtualView(int id, AccessibilityEvent event) {
-            if (id == ExploreByTouchHelper.INVALID_ID) {
-                throw new IllegalArgumentException("Invalid virtual view id");
-            }
-            // We're required to set something here.
-            event.setContentDescription("");
-        }
-
-        @Override
-        protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
-            if (id == ExploreByTouchHelper.INVALID_ID) {
-                throw new IllegalArgumentException("Invalid virtual view id");
-            }
-
-            node.setContentDescription(getLocationDescriptionForIconDrop(id));
-            node.setBoundsInParent(getItemBounds(id));
-
-            node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
-            node.setClickable(true);
-            node.setFocusable(true);
-        }
-
-        private String getLocationDescriptionForIconDrop(int id) {
-            LauncherAccessibilityDelegate delegate =
-                    LauncherAppState.getInstance().getAccessibilityDelegate();
-            if (delegate == null) {
-                return "";
-            }
-
-            int y = id % mCountY;
-            int x = id / mCountY;
-            LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
-
-            Resources res = getContext().getResources();
-            View child = getChildAt(x, y);
-            if (child == null || child == dragInfo.item) {
-                return res.getString(R.string.move_to_empty_cell, x + 1, y + 1);
-            } else {
-                ItemInfo info = (ItemInfo) child.getTag();
-                if (info instanceof AppInfo || info instanceof ShortcutInfo) {
-                    return res.getString(R.string.create_folder_with, info.title);
-                } else if (info instanceof FolderInfo) {
-                    return res.getString(R.string.add_to_folder, info.title);
-                }
-            }
-            return "";
-        }
-
-        private String getConfirmationForIconDrop(int id) {
-            LauncherAccessibilityDelegate delegate =
-                LauncherAppState.getInstance().getAccessibilityDelegate();
-            if (delegate == null) {
-                return "";
-            }
-
-            int y = id % mCountY;
-            int x = id / mCountY;
-            LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo();
-
-            Resources res = getContext().getResources();
-            View child = getChildAt(x, y);
-            if (child == null || child == dragInfo.item) {
-                return res.getString(R.string.item_moved);
-            } else {
-                ItemInfo info = (ItemInfo) child.getTag();
-                if (info instanceof AppInfo || info instanceof ShortcutInfo) {
-                    return res.getString(R.string.folder_created);
-
-                } else if (info instanceof FolderInfo) {
-                    return res.getString(R.string.added_to_folder);
-                }
-            }
-            return "";
-        }
-
-        private Rect getItemBounds(int id) {
-            int cellY = id % mCountY;
-            int cellX = id / mCountY;
-            int x = getPaddingLeft() + (int) (cellX * (mCellWidth + mWidthGap));
-            int y = getPaddingTop() + (int) (cellY * (mCellHeight + mHeightGap));
-
-            Rect bounds = mTempRect;
-            bounds.set(x, y, x + mCellWidth, y + mCellHeight);
-            return bounds;
-        }
-    }
-
     public void enableHardwareLayer(boolean hasLayer) {
         mShortcutsAndWidgets.setLayerType(hasLayer ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE, sPaint);
     }
@@ -897,6 +647,10 @@ public class CellLayout extends ViewGroup {
         mShortcutsAndWidgets.setIsHotseat(isHotseat);
     }
 
+    public boolean isHotseat() {
+        return mIsHotseat;
+    }
+
     public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
             boolean markCells) {
         final LayoutParams lp = params;
@@ -982,7 +736,7 @@ public class CellLayout extends ViewGroup {
      * @param y Y coordinate of the point
      * @param result Array of 2 ints to hold the x and y coordinate of the cell
      */
-    void pointToCellExact(int x, int y, int[] result) {
+    public void pointToCellExact(int x, int y, int[] result) {
         final int hStartPadding = getPaddingLeft();
         final int vStartPadding = getPaddingTop();
 
index 3b21c2b..a896099 100644 (file)
@@ -658,6 +658,7 @@ public class DragController {
         mDragObject.y = coordinates[1];
         checkTouchMove(dropTarget);
 
+        dropTarget.prepareAccessibilityDrop();
         // Perform the drop
         drop(location[0], location[1]);
         endDrag();
index c5cca3b..3628e57 100644 (file)
@@ -183,6 +183,8 @@ public interface DropTarget {
      */
     boolean acceptDrop(DragObject dragObject);
 
+    void prepareAccessibilityDrop();
+
     // These methods are implemented in Views
     void getHitRectRelativeToDragLayer(Rect outRect);
     void getLocationInDragLayer(int[] loc);
index 03a9019..e0aecea 100644 (file)
@@ -49,8 +49,10 @@ import android.view.inputmethod.InputMethodManager;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import com.android.launcher3.CellLayout.CellInfo;
 import com.android.launcher3.DragController.DragListener;
 import com.android.launcher3.FolderInfo.FolderListener;
+import com.android.launcher3.LauncherAccessibilityDelegate.AccessibilityDragSource;
 import com.android.launcher3.UninstallDropTarget.UninstallSource;
 import com.android.launcher3.Workspace.ItemOperator;
 import com.android.launcher3.util.Thunk;
@@ -63,7 +65,7 @@ import java.util.Collections;
  */
 public class Folder extends LinearLayout implements DragSource, View.OnClickListener,
         View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
-        View.OnFocusChangeListener, DragListener, UninstallSource {
+        View.OnFocusChangeListener, DragListener, UninstallSource, AccessibilityDragSource {
     private static final String TAG = "Launcher.Folder";
 
     /**
@@ -237,7 +239,10 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
     public boolean onLongClick(View v) {
         // Return if global dragging is not enabled
         if (!mLauncher.isDraggingEnabled()) return true;
+        return beginDrag(v, false);
+    }
 
+    private boolean beginDrag(View v, boolean accessible) {
         Object tag = v.getTag();
         if (tag instanceof ShortcutInfo) {
             ShortcutInfo item = (ShortcutInfo) tag;
@@ -245,7 +250,7 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
                 return false;
             }
 
-            mLauncher.getWorkspace().beginDragShared(v, new Point(), this, false);
+            mLauncher.getWorkspace().beginDragShared(v, new Point(), this, accessible);
 
             mCurrentDragInfo = item;
             mEmptyCellRank = item.rank;
@@ -259,6 +264,20 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
         return true;
     }
 
+    @Override
+    public void startDrag(CellInfo cellInfo, boolean accessible) {
+        beginDrag(cellInfo.cell, accessible);
+    }
+
+    @Override
+    public void enableAccessibleDrag(boolean enable) {
+        mLauncher.getSearchBar().enableAccessibleDrag(enable);
+        for (int i = 0; i < mContent.getChildCount(); i++) {
+            mContent.getPageAt(i).enableAccessibleDrag(enable, CellLayout.FOLDER_ACCESSIBILITY_DRAG);
+        }
+        mLauncher.getWorkspace().setAddNewPageOnDrag(!enable);
+    }
+
     public boolean isEditingName() {
         return mIsEditingName;
     }
@@ -711,6 +730,18 @@ public class Folder extends LinearLayout implements DragSource, View.OnClickList
         }
     }
 
+    /**
+     * When performing an accessibility drop, onDrop is sent immediately after onDragEnter. So we
+     * need to complete all transient states based on timers.
+     */
+    @Override
+    public void prepareAccessibilityDrop() {
+        if (mReorderAlarm.alarmPending()) {
+            mReorderAlarm.cancelAlarm();
+            mReorderAlarmListener.onAlarm(mReorderAlarm);
+        }
+    }
+
     public void onDropCompleted(final View target, final DragObject d,
             final boolean isFlingToDelete, final boolean success) {
         if (mDeferDropAfterUninstall) {
index a60e160..4255fa4 100644 (file)
@@ -25,24 +25,25 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate {
     public static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
     public static final int MOVE = R.id.action_move;
 
-    enum DragType {
+    public enum DragType {
         ICON,
         FOLDER,
         WIDGET
     }
 
     public static class DragInfo {
-        DragType dragType;
-        ItemInfo info;
-        View item;
+        public DragType dragType;
+        public ItemInfo info;
+        public View item;
     }
 
-    private DragInfo mDragInfo = null;
-
     private final SparseArray<AccessibilityAction> mActions =
             new SparseArray<AccessibilityAction>();
     @Thunk final Launcher mLauncher;
 
+    private DragInfo mDragInfo = null;
+    private AccessibilityDragSource mDragSource = null;
+
     public LauncherAccessibilityDelegate(Launcher launcher) {
         mLauncher = launcher;
 
@@ -197,10 +198,23 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate {
 
         Rect pos = new Rect();
         mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
-
         mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
-        mLauncher.getWorkspace().enableAccessibleDrag(true);
-        mLauncher.getWorkspace().startDrag(cellInfo, true);
+
+        Workspace workspace = mLauncher.getWorkspace();
+
+        Folder folder = workspace.getOpenFolder();
+        if (folder != null) {
+            if (folder.getItemsInReadingOrder().contains(item)) {
+                mDragSource = folder;
+            } else {
+                mLauncher.closeFolder();
+            }
+        }
+        if (mDragSource == null) {
+            mDragSource = workspace;
+        }
+        mDragSource.enableAccessibleDrag(true);
+        mDragSource.startDrag(cellInfo, true);
     }
 
     public boolean onBackPressed() {
@@ -218,6 +232,15 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate {
 
     private void endAccessibleDrag() {
         mDragInfo = null;
-        mLauncher.getWorkspace().enableAccessibleDrag(false);
+        if (mDragSource != null) {
+            mDragSource.enableAccessibleDrag(false);
+            mDragSource = null;
+        }
+    }
+
+    public static interface AccessibilityDragSource {
+        void startDrag(CellLayout.CellInfo cellInfo, boolean accessible);
+
+        void enableAccessibleDrag(boolean enable);
     }
 }
index 2efd207..07d1c98 100644 (file)
@@ -60,6 +60,7 @@ import android.widget.TextView;
 import com.android.launcher3.FolderIcon.FolderRingAnimator;
 import com.android.launcher3.Launcher.CustomContentCallbacks;
 import com.android.launcher3.Launcher.LauncherOverlay;
+import com.android.launcher3.LauncherAccessibilityDelegate.AccessibilityDragSource;
 import com.android.launcher3.LauncherSettings.Favorites;
 import com.android.launcher3.UninstallDropTarget.UninstallSource;
 import com.android.launcher3.compat.UserHandleCompat;
@@ -82,7 +83,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 public class Workspace extends SmoothPagedView
         implements DropTarget, DragSource, DragScroller, View.OnTouchListener,
         DragController.DragListener, LauncherTransitionable, ViewGroup.OnHierarchyChangeListener,
-        Insettable, UninstallSource {
+        Insettable, UninstallSource, AccessibilityDragSource {
     private static final String TAG = "Launcher.Workspace";
 
     private static final int CHILDREN_OUTLINE_FADE_OUT_DELAY = 0;
@@ -125,6 +126,7 @@ public class Workspace extends SmoothPagedView
 
     @Thunk Runnable mRemoveEmptyScreenRunnable;
     @Thunk boolean mDeferRemoveExtraEmptyScreen = false;
+    @Thunk boolean mAddNewPageOnDrag = true;
 
     /**
      * CellInfo for the cell that is currently being dragged
@@ -390,7 +392,7 @@ public class Workspace extends SmoothPagedView
         post(new Runnable() {
             @Override
             public void run() {
-                if (mIsDragOccuring) {
+                if (mIsDragOccuring && mAddNewPageOnDrag) {
                     mDeferRemoveExtraEmptyScreen = false;
                     addExtraEmptyScreenOnDrag();
                 }
@@ -398,6 +400,9 @@ public class Workspace extends SmoothPagedView
         });
     }
 
+    public void setAddNewPageOnDrag(boolean addPage) {
+        mAddNewPageOnDrag = addPage;
+    }
 
     public void deferRemoveExtraEmptyScreen() {
         mDeferRemoveExtraEmptyScreen = true;
@@ -562,7 +567,7 @@ public class Workspace extends SmoothPagedView
         LauncherAccessibilityDelegate delegate =
                 LauncherAppState.getInstance().getAccessibilityDelegate();
         if (delegate != null && delegate.isInAccessibleDrag()) {
-            newScreen.enableAccessibleDrag(true);
+            newScreen.enableAccessibleDrag(true, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
         }
         return screenId;
     }
@@ -1601,10 +1606,11 @@ public class Workspace extends SmoothPagedView
     }
 
     @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+    @Override
     public void enableAccessibleDrag(boolean enable) {
         for (int i = 0; i < getChildCount(); i++) {
             CellLayout child = (CellLayout) getChildAt(i);
-            child.enableAccessibleDrag(enable);
+            child.enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
         }
 
         if (enable) {
@@ -1615,7 +1621,8 @@ public class Workspace extends SmoothPagedView
             setOnClickListener(mLauncher);
         }
         mLauncher.getSearchBar().enableAccessibleDrag(enable);
-        mLauncher.getHotseat().getLayout().enableAccessibleDrag(enable);
+        mLauncher.getHotseat().getLayout()
+            .enableAccessibleDrag(enable, CellLayout.WORKSPACE_ACCESSIBILITY_DRAG);
     }
 
     public boolean hasCustomContent() {
@@ -2262,6 +2269,7 @@ public class Workspace extends SmoothPagedView
         startDrag(cellInfo, false);
     }
 
+    @Override
     public void startDrag(CellLayout.CellInfo cellInfo, boolean accessible) {
         View child = cellInfo.cell;
 
@@ -2625,6 +2633,9 @@ public class Workspace extends SmoothPagedView
         return false;
     }
 
+    @Override
+    public void prepareAccessibilityDrop() { }
+
     public void onDrop(final DragObject d) {
         mDragViewVisualCenter = d.getVisualCenter(mDragViewVisualCenter);
         CellLayout dropTargetLayout = mDropToLayout;
diff --git a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
new file mode 100644 (file)
index 0000000..0f17241
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * 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.graphics.Rect;
+import android.os.Bundle;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.widget.ExploreByTouchHelper;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.accessibility.AccessibilityEvent;
+
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.LauncherAccessibilityDelegate;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.R;
+
+import java.util.List;
+
+/**
+ * Helper class to make drag-and-drop in a {@link CellLayout} accessible.
+ */
+public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHelper
+        implements OnClickListener {
+    protected static final int INVALID_POSITION = -1;
+
+    private static final int[] sTempArray = new int[2];
+
+    protected final CellLayout mView;
+    protected final Context mContext;
+    protected final LauncherAccessibilityDelegate mDelegate;
+
+    private final Rect mTempRect = new Rect();
+
+    public DragAndDropAccessibilityDelegate(CellLayout forView) {
+        super(forView);
+        mView = forView;
+        mContext = mView.getContext();
+        mDelegate = LauncherAppState.getInstance().getAccessibilityDelegate();
+    }
+
+    @Override
+    protected int getVirtualViewAt(float x, float y) {
+        if (x < 0 || y < 0 || x > mView.getMeasuredWidth() || y > mView.getMeasuredHeight()) {
+            return INVALID_ID;
+        }
+        mView.pointToCellExact((int) x, (int) y, sTempArray);
+
+        // Map cell to id
+        int id = sTempArray[0] + sTempArray[1] * mView.getCountX();
+        return intersectsValidDropTarget(id);
+    }
+
+    /**
+     * @return the view id of the top left corner of a valid drop region or
+     * {@link #INVALID_POSITION} if there is no such valid region.
+     */
+    protected abstract int intersectsValidDropTarget(int id);
+
+    @Override
+    protected void getVisibleVirtualViews(List<Integer> virtualViews) {
+        // We create a virtual view for each cell of the grid
+        // The cell ids correspond to cells in reading order.
+        int nCells = mView.getCountX() * mView.getCountY();
+
+        for (int i = 0; i < nCells; i++) {
+            if (intersectsValidDropTarget(i) == i) {
+                virtualViews.add(i);
+            }
+        }
+    }
+
+    @Override
+    protected boolean onPerformActionForVirtualView(int viewId, int action, Bundle args) {
+        if (action == AccessibilityNodeInfoCompat.ACTION_CLICK && viewId != INVALID_ID) {
+            String confirmation = getConfirmationForIconDrop(viewId);
+            mDelegate.handleAccessibleDrop(mView, getItemBounds(viewId), confirmation);
+            return true;
+        }
+        return false;
+    }
+
+    @Override
+    public void onClick(View v) {
+        onPerformActionForVirtualView(getFocusedVirtualView(),
+                AccessibilityNodeInfoCompat.ACTION_CLICK, null);
+    }
+
+    @Override
+    protected void onPopulateEventForVirtualView(int id, AccessibilityEvent event) {
+        if (id == INVALID_ID) {
+            throw new IllegalArgumentException("Invalid virtual view id");
+        }
+        event.setContentDescription(mContext.getString(R.string.action_move_here));
+    }
+
+    @Override
+    protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
+        if (id == INVALID_ID) {
+            throw new IllegalArgumentException("Invalid virtual view id");
+        }
+
+        node.setContentDescription(getLocationDescriptionForIconDrop(id));
+        node.setBoundsInParent(getItemBounds(id));
+
+        node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
+        node.setClickable(true);
+        node.setFocusable(true);
+    }
+
+    protected abstract String getLocationDescriptionForIconDrop(int id);
+
+    protected abstract String getConfirmationForIconDrop(int id);
+
+    private Rect getItemBounds(int id) {
+        int cellX = id % mView.getCountX();
+        int cellY = id / mView.getCountX();
+        LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
+        mView.cellToRect(cellX, cellY, dragInfo.info.spanX, dragInfo.info.spanY, mTempRect);
+        return mTempRect;
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
new file mode 100644 (file)
index 0000000..fc105b4
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * 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 com.android.launcher3.CellLayout;
+import com.android.launcher3.FolderPagedView;
+import com.android.launcher3.R;
+
+/**
+ * Implementation of {@link DragAndDropAccessibilityDelegate} to support DnD in a folder.
+ */
+public class FolderAccessibilityHelper extends DragAndDropAccessibilityDelegate {
+    private final int mStartPosition;
+
+    public FolderAccessibilityHelper(CellLayout layout) {
+        super(layout);
+        FolderPagedView parent = (FolderPagedView) layout.getParent();
+
+        int index = parent.indexOfChild(layout);
+        mStartPosition = 1 + index * layout.getCountX() * layout.getCountY();
+    }
+    @Override
+    protected int intersectsValidDropTarget(int id) {
+        return id;
+    }
+
+    @Override
+    protected String getLocationDescriptionForIconDrop(int id) {
+        return mContext.getString(R.string.move_to_position, id + mStartPosition);
+    }
+
+    @Override
+    protected String getConfirmationForIconDrop(int id) {
+        return mContext.getString(R.string.item_moved);
+    }
+}
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
new file mode 100644 (file)
index 0000000..42e9e3c
--- /dev/null
@@ -0,0 +1,167 @@
+/*
+ * 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.text.TextUtils;
+import android.view.View;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAccessibilityDelegate;
+import com.android.launcher3.LauncherAccessibilityDelegate.DragType;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+
+/**
+ * Implementation of {@link DragAndDropAccessibilityDelegate} to support DnD on workspace.
+ */
+public class WorkspaceAccessibilityHelper extends DragAndDropAccessibilityDelegate {
+
+    public WorkspaceAccessibilityHelper(CellLayout layout) {
+        super(layout);
+    }
+
+    /**
+     * Find the virtual view id corresponding to the top left corner of any drop region by which
+     * the passed id is contained. For an icon, this is simply
+     */
+    @Override
+    protected int intersectsValidDropTarget(int id) {
+        int mCountX = mView.getCountX();
+        int mCountY = mView.getCountY();
+
+        int x = id % mCountX;
+        int y = id / mCountX;
+        LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
+
+        if (dragInfo.dragType == DragType.WIDGET && mView.isHotseat()) {
+            return INVALID_POSITION;
+        }
+
+        if (dragInfo.dragType == DragType.WIDGET) {
+            // For a widget, every cell must be vacant. In addition, we will return any valid
+            // drop target by which the passed id is contained.
+            boolean fits = false;
+
+            // These represent the amount that we can back off if we hit a problem. They
+            // get consumed as we move up and to the right, trying new regions.
+            int spanX = dragInfo.info.spanX;
+            int spanY = dragInfo.info.spanY;
+
+            for (int m = 0; m < spanX; m++) {
+                for (int n = 0; n < spanY; n++) {
+
+                    fits = true;
+                    int x0 = x - m;
+                    int y0 = y - n;
+
+                    if (x0 < 0 || y0 < 0) continue;
+
+                    for (int i = x0; i < x0 + spanX; i++) {
+                        if (!fits) break;
+                        for (int j = y0; j < y0 + spanY; j++) {
+                            if (i >= mCountX || j >= mCountY || mView.isOccupied(i, j)) {
+                                fits = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (fits) {
+                        return x0 + mCountX * y0;
+                    }
+                }
+            }
+            return INVALID_POSITION;
+        } else {
+            // For an icon, we simply check the view directly below
+            View child = mView.getChildAt(x, y);
+            if (child == null || child == dragInfo.item) {
+                // Empty cell. Good for an icon or folder.
+                return id;
+            } else if (dragInfo.dragType != DragType.FOLDER) {
+                // For icons, we can consider cells that have another icon or a folder.
+                ItemInfo info = (ItemInfo) child.getTag();
+                if (info instanceof AppInfo || info instanceof FolderInfo ||
+                        info instanceof ShortcutInfo) {
+                    return id;
+                }
+            }
+            return INVALID_POSITION;
+        }
+    }
+
+    @Override
+    protected String getConfirmationForIconDrop(int id) {
+        int x = id % mView.getCountX();
+        int y = id / mView.getCountX();
+        LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
+
+        View child = mView.getChildAt(x, y);
+        if (child == null || child == dragInfo.item) {
+            return mContext.getString(R.string.item_moved);
+        } else {
+            ItemInfo info = (ItemInfo) child.getTag();
+            if (info instanceof AppInfo || info instanceof ShortcutInfo) {
+                return mContext.getString(R.string.folder_created);
+
+            } else if (info instanceof FolderInfo) {
+                return mContext.getString(R.string.added_to_folder);
+            }
+        }
+        return "";
+    }
+
+    @Override
+    protected String getLocationDescriptionForIconDrop(int id) {
+        int x = id % mView.getCountX();
+        int y = id / mView.getCountX();
+        LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
+
+        View child = mView.getChildAt(x, y);
+        if (child == null || child == dragInfo.item) {
+            if (mView.isHotseat()) {
+                return mContext.getString(R.string.move_to_hotseat_position, id + 1);
+            } else {
+                return mContext.getString(R.string.move_to_empty_cell, y + 1, x + 1);
+            }
+        } else {
+            ItemInfo info = (ItemInfo) child.getTag();
+            if (info instanceof ShortcutInfo) {
+                return mContext.getString(R.string.create_folder_with, info.title);
+            } else if (info instanceof FolderInfo) {
+                if (TextUtils.isEmpty(info.title.toString().trim())) {
+                    // Find the first item in the folder.
+                    FolderInfo folder = (FolderInfo) info;
+                    ShortcutInfo firstItem = null;
+                    for (ShortcutInfo shortcut : folder.contents) {
+                        if (firstItem == null || firstItem.rank > shortcut.rank) {
+                            firstItem = shortcut;
+                        }
+                    }
+
+                    if (firstItem != null) {
+                        return mContext.getString(R.string.add_to_folder_with_app, firstItem.title);
+                    }
+                }
+                return mContext.getString(R.string.add_to_folder, info.title);
+            }
+        }
+        return "";
+    }
+}