From: Sunny Goyal Date: Fri, 24 Apr 2015 18:44:51 +0000 (-0700) Subject: Enabling accessibility drag and drop in folder X-Git-Tag: android-x86-7.1-r1~166^2~557^2 X-Git-Url: http://git.osdn.net/view?a=commitdiff_plain;h=e9b651eef1b9f3647eba94f833bff3fc52f5956b;p=android-x86%2Fpackages-apps-Launcher3.git Enabling accessibility drag and drop in folder > Moving DragAndDropAccessibilityDelegate to a separate class > Using getFocusedVirtualView() instead of using DownX and downY > Updating various accessibility strings Bug: 19776741 Change-Id: I85c2551d4d6172c30702e68f41b114bb999655b6 --- diff --git a/res/values/strings.xml b/res/values/strings.xml index a68f53a91..3b94eb305 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -307,10 +307,13 @@ s --> - Add to workspace + Add to home screen + + + Move here - Item added to workspace + Item added to home screen Item removed @@ -319,7 +322,13 @@ s --> Move Item - Move to empty cell %1$s, %2$s + Move to row %1$s column %2$s + + + Move to position %1$s + + + Move to favorites position %1$s Item moved @@ -327,6 +336,9 @@ s --> Add to folder: %1$s + + Add to folder with %1$s + Item added to folder diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java index fb49df5df..daa07d08d 100644 --- a/src/com/android/launcher3/ButtonDropTarget.java +++ b/src/com/android/launcher3/ButtonDropTarget.java @@ -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 diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 85653bef7..f5d2f7dc6 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -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 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(); diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java index 3b21c2b55..a8960996e 100644 --- a/src/com/android/launcher3/DragController.java +++ b/src/com/android/launcher3/DragController.java @@ -658,6 +658,7 @@ public class DragController { mDragObject.y = coordinates[1]; checkTouchMove(dropTarget); + dropTarget.prepareAccessibilityDrop(); // Perform the drop drop(location[0], location[1]); endDrag(); diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index c5cca3b28..3628e573d 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -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); diff --git a/src/com/android/launcher3/Folder.java b/src/com/android/launcher3/Folder.java index 03a9019e8..e0aeceae8 100644 --- a/src/com/android/launcher3/Folder.java +++ b/src/com/android/launcher3/Folder.java @@ -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) { diff --git a/src/com/android/launcher3/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/LauncherAccessibilityDelegate.java index a60e16024..4255fa466 100644 --- a/src/com/android/launcher3/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/LauncherAccessibilityDelegate.java @@ -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 mActions = new SparseArray(); @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); } } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 2efd20739..07d1c98f1 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -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 index 000000000..0f1724155 --- /dev/null +++ b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java @@ -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 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 index 000000000..fc105b4a4 --- /dev/null +++ b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java @@ -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 index 000000000..42e9e3c58 --- /dev/null +++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java @@ -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 ""; + } +}