diff options
author | Adam Cohen <adamcohen@google.com> | 2015-01-23 16:11:55 -0800 |
---|---|---|
committer | Adam Cohen <adamcohen@google.com> | 2015-03-09 11:29:28 -0700 |
commit | c9735cff2e558aa3f3810e49c15ef13049b9429c (patch) | |
tree | 13a6513604861f1e19f2f8ce1ca0f99d9b4a196f /src/com/android | |
parent | 8dbf0fff1f424df8db1bbedfe8e5cfd7783a92ea (diff) | |
download | android_packages_apps_Trebuchet-c9735cff2e558aa3f3810e49c15ef13049b9429c.tar.gz android_packages_apps_Trebuchet-c9735cff2e558aa3f3810e49c15ef13049b9429c.tar.bz2 android_packages_apps_Trebuchet-c9735cff2e558aa3f3810e49c15ef13049b9429c.zip |
Enabling accessible drag and drop
-> Using the context menu, and a new two stage system, this allows
users to curate icons and widgets on the workspace
-> Move icons / widgets to any empty cell on any existing screen, or
create a new screen (appended to the right, as with regular drag
and drop)
-> Move icons into existing folders
-> Create folders by moving an icon onto another icon
-> Also added confirmations for these and some existing accessibility actions
Limitations:
-> Currently, no support for drag and drop in folders
-> Considering moving the drag view so it doesn't occlude any
content (in particular, when user changes pages)
-> In this mode, accessibility framework seems to have
problems with the next / prev operations
Bug: 18482913
Change-Id: I19b0be9dc8bfa766d430408c8ad9303c716b89b2
Diffstat (limited to 'src/com/android')
-rw-r--r-- | src/com/android/launcher3/CellLayout.java | 306 | ||||
-rw-r--r-- | src/com/android/launcher3/DragController.java | 56 | ||||
-rw-r--r-- | src/com/android/launcher3/DropTarget.java | 3 | ||||
-rw-r--r-- | src/com/android/launcher3/Launcher.java | 6 | ||||
-rw-r--r-- | src/com/android/launcher3/LauncherAccessibilityDelegate.java | 107 | ||||
-rw-r--r-- | src/com/android/launcher3/LauncherAppState.java | 4 | ||||
-rw-r--r-- | src/com/android/launcher3/SearchDropTargetBar.java | 22 | ||||
-rw-r--r-- | src/com/android/launcher3/Workspace.java | 63 |
8 files changed, 525 insertions, 42 deletions
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index a3500aa82..c57090d7c 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -22,6 +22,7 @@ import android.animation.AnimatorSet; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; @@ -33,25 +34,34 @@ import android.graphics.Point; 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; import android.view.MotionEvent; import android.view.View; +import android.view.View.OnClickListener; import android.view.ViewDebug; import android.view.ViewGroup; +import android.view.accessibility.AccessibilityEvent; import android.view.animation.Animation; import android.view.animation.DecelerateInterpolator; import android.view.animation.LayoutAnimationController; import com.android.launcher3.FolderIcon.FolderRingAnimator; +import com.android.launcher3.LauncherAccessibilityDelegate.DragType; import java.util.ArrayList; 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 { @@ -169,6 +179,14 @@ public class CellLayout extends ViewGroup { private final static Paint sPaint = new Paint(); + // Related to accessible drag and drop + DragAndDropAccessibilityDelegate mTouchHelper = new DragAndDropAccessibilityDelegate(this); + private boolean mUseTouchHelper = false; + OnClickListener mOldClickListener = null; + OnClickListener mOldWorkspaceListener = null; + private int mDownX = 0; + private int mDownY = 0; + public CellLayout(Context context) { this(context, null); } @@ -294,6 +312,282 @@ public class CellLayout extends ViewGroup { addView(mShortcutsAndWidgets); } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public void enableAccessibleDrag(boolean enable) { + mUseTouchHelper = enable; + if (!enable) { + ViewCompat.setAccessibilityDelegate(this, null); + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO); + setOnClickListener(mLauncher); + } else { + ViewCompat.setAccessibilityDelegate(this, mTouchHelper); + setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + getShortcutsAndWidgets().setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); + setOnClickListener(mTouchHelper); + } + + // Invalidate the accessibility hierarchy + if (getParent() != null) { + getParent().notifySubtreeAccessibilityStateChanged( + this, this, AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE); + } + } + + @Override + public boolean dispatchHoverEvent(MotionEvent event) { + // Always attempt to dispatch hover events to accessibility first. + if (mUseTouchHelper && mTouchHelper.dispatchHoverEvent(event)) { + return true; + } + return super.dispatchHoverEvent(event); + } + + @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))) { + return true; + } + 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(); + LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo(); + + int y = id % mCountY; + int x = id / mCountY; + + 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) { + if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) { + String confirmation = getConfirmationForIconDrop(viewId); + LauncherAppState.getInstance().getAccessibilityDelegate() + .handleAccessibleDrop(CellLayout.this, getItemBounds(viewId), confirmation); + return true; + } + return false; + } + + @Override + public void onClick(View arg0) { + int viewId = getViewIdAt(mDownX, mDownY); + + String confirmation = getConfirmationForIconDrop(viewId); + LauncherAppState.getInstance().getAccessibilityDelegate() + .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(); + LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo(); + + int y = id % mCountY; + int x = id / mCountY; + + 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, y); + } 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(); + LauncherAccessibilityDelegate.DragInfo dragInfo = delegate.getDragInfo(); + + int y = id % mCountY; + int x = id / mCountY; + + 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); } @@ -679,18 +973,6 @@ public class CellLayout extends ViewGroup { mShortcutsAndWidgets.removeViewsInLayout(start, count); } - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - // First we clear the tag to ensure that on every touch down we start with a fresh slate, - // even in the case where we return early. Not clearing here was causing bugs whereby on - // long-press we'd end up picking up an item from a previous drag operation. - if (mInterceptTouchListener != null && mInterceptTouchListener.onTouch(this, ev)) { - return true; - } - - return false; - } - /** * Given a point, return the cell that strictly encloses that point * @param x X coordinate of the point diff --git a/src/com/android/launcher3/DragController.java b/src/com/android/launcher3/DragController.java index 480dce999..09c59a057 100644 --- a/src/com/android/launcher3/DragController.java +++ b/src/com/android/launcher3/DragController.java @@ -73,6 +73,9 @@ public class DragController { /** Whether or not we're dragging. */ private boolean mDragging; + /** Whether or not this is an accessible drag operation */ + private boolean mIsAccessibleDrag; + /** X coordinate of the down event. */ private int mMotionDownX; @@ -182,7 +185,7 @@ public class DragController { (int) ((initialDragViewScale * bmp.getHeight() - bmp.getHeight()) / 2); startDrag(bmp, dragLayerX, dragLayerY, source, dragInfo, dragAction, null, - null, initialDragViewScale); + null, initialDragViewScale, false); if (dragAction == DRAG_ACTION_MOVE) { v.setVisibility(View.GONE); @@ -202,10 +205,11 @@ public class DragController { * {@link #DRAG_ACTION_COPY} * @param dragRegion Coordinates within the bitmap b for the position of item being dragged. * Makes dragging feel more precise, e.g. you can clip out a transparent border + * @param accessible whether this drag should occur in accessibility mode */ public DragView startDrag(Bitmap b, int dragLayerX, int dragLayerY, DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion, - float initialDragViewScale) { + float initialDragViewScale, boolean accessible) { if (PROFILE_DRAWING_DURING_DRAG) { android.os.Debug.startMethodTracing("Launcher"); } @@ -228,12 +232,21 @@ public class DragController { final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top; mDragging = true; + mIsAccessibleDrag = accessible; mDragObject = new DropTarget.DragObject(); mDragObject.dragComplete = false; - mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft); - mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); + if (mIsAccessibleDrag) { + // For an accessible drag, we assume the view is being dragged from the center. + mDragObject.xOffset = b.getWidth() / 2; + mDragObject.yOffset = b.getHeight() / 2; + mDragObject.accessibleDrag = true; + } else { + mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft); + mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop); + } + mDragObject.dragSource = source; mDragObject.dragInfo = dragInfo; @@ -349,6 +362,7 @@ public class DragController { private void endDrag() { if (mDragging) { mDragging = false; + mIsAccessibleDrag = false; clearScrollRunnable(); boolean isDeferred = false; if (mDragObject.dragView != null) { @@ -421,6 +435,10 @@ public class DragController { + mDragging); } + if (mIsAccessibleDrag) { + return false; + } + // Update the velocity tracker acquireVelocityTrackerAndAddMovement(ev); @@ -560,7 +578,7 @@ public class DragController { * Call this from a drag source view. */ public boolean onTouchEvent(MotionEvent ev) { - if (!mDragging) { + if (!mDragging || mIsAccessibleDrag) { return false; } @@ -617,6 +635,34 @@ public class DragController { } /** + * Since accessible drag and drop won't cause the same sequence of touch events, we manually + * inject the appropriate state. + */ + public void prepareAccessibleDrag(int x, int y) { + mMotionDownX = x; + mMotionDownY = y; + mLastDropTarget = null; + } + + /** + * As above, since accessible drag and drop won't cause the same sequence of touch events, + * we manually ensure appropriate drag and drop events get emulated for accessible drag. + */ + public void completeAccessibleDrag(int[] location) { + final int[] coordinates = mCoordinatesTemp; + + // We make sure that we prime the target for drop. + DropTarget dropTarget = findDropTarget(location[0], location[1], coordinates); + mDragObject.x = coordinates[0]; + mDragObject.y = coordinates[1]; + checkTouchMove(dropTarget); + + // Perform the drop + drop(location[0], location[1]); + endDrag(); + } + + /** * Determines whether the user flung the current item to delete it. * * @return the vector at which the item was flung, or null if no fling was detected. diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java index 7ede42751..94ae82b82 100644 --- a/src/com/android/launcher3/DropTarget.java +++ b/src/com/android/launcher3/DropTarget.java @@ -54,6 +54,9 @@ public interface DropTarget { /** Where the drag originated */ public DragSource dragSource = null; + /** The object is part of an accessible drag operation */ + public boolean accessibleDrag; + /** Post drag animation runnable */ public Runnable postAnimationRunnable = null; diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 1ad8b274d..e58d0dab4 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -2447,6 +2447,10 @@ public class Launcher extends Activity return; } + if (LauncherAppState.getInstance().getAccessibilityDelegate().onBackPressed()) { + return; + } + if (isAllAppsVisible()) { if (mAppsCustomizeContent.getContentType() == AppsCustomizePagedView.ContentType.Applications) { @@ -3165,7 +3169,7 @@ public class Launcher extends Activity View itemUnderLongClick = null; if (v.getTag() instanceof ItemInfo) { ItemInfo info = (ItemInfo) v.getTag(); - longClickCellInfo = new CellLayout.CellInfo(v, info);; + longClickCellInfo = new CellLayout.CellInfo(v, info); itemUnderLongClick = longClickCellInfo.cell; resetAddInfo(); } diff --git a/src/com/android/launcher3/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/LauncherAccessibilityDelegate.java index c9e277e4c..0ae1c0e90 100644 --- a/src/com/android/launcher3/LauncherAccessibilityDelegate.java +++ b/src/com/android/launcher3/LauncherAccessibilityDelegate.java @@ -1,11 +1,16 @@ package com.android.launcher3; import android.annotation.TargetApi; +import android.content.res.Resources; +import android.graphics.Rect; import android.os.Build; import android.os.Bundle; +import android.support.v4.view.accessibility.AccessibilityEventCompat; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; import android.util.SparseArray; import android.view.View; import android.view.View.AccessibilityDelegate; +import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; @@ -20,6 +25,21 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { public static final int INFO = R.id.action_info; public static final int UNINSTALL = R.id.action_uninstall; public static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace; + public static final int MOVE = R.id.action_move; + + enum DragType { + ICON, + FOLDER, + WIDGET + } + + public static class DragInfo { + DragType dragType; + ItemInfo info; + View item; + } + + private DragInfo mDragInfo = null; private final SparseArray<AccessibilityAction> mActions = new SparseArray<AccessibilityAction>(); @@ -36,6 +56,9 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { launcher.getText(R.string.delete_target_uninstall_label))); mActions.put(ADD_TO_WORKSPACE, new AccessibilityAction(ADD_TO_WORKSPACE, launcher.getText(R.string.action_add_to_workspace))); + mActions.put(MOVE, new AccessibilityAction(MOVE, + launcher.getText(R.string.action_move))); + } @Override @@ -49,6 +72,7 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { || (item instanceof FolderInfo)) { // Workspace shortcut / widget info.addAction(mActions.get(REMOVE)); + info.addAction(mActions.get(MOVE)); } else if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) { // App or Widget from customization tray if (item instanceof AppInfo) { @@ -69,14 +93,21 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { } public boolean performAction(View host, ItemInfo item, int action) { + Resources res = mLauncher.getResources(); if (action == REMOVE) { - return DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host); + if (DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host)) { + announceConfirmation(R.string.item_removed_from_workspace); + return true; + } + return false; } else if (action == INFO) { InfoDropTarget.startDetailsActivityForInfo(item, mLauncher); return true; } else if (action == UNINSTALL) { DeleteDropTarget.uninstallApp(mLauncher, (AppInfo) item); return true; + } else if (action == MOVE) { + beginAccessibleDrag(host, item); } else if (action == ADD_TO_WORKSPACE) { final int preferredPage = mLauncher.getWorkspace().getCurrentPage(); final ScreenPosProvider screenProvider = new ScreenPosProvider() { @@ -90,20 +121,92 @@ public class LauncherAccessibilityDelegate extends AccessibilityDelegate { final ArrayList<ItemInfo> addShortcuts = new ArrayList<ItemInfo>(); addShortcuts.add(((AppInfo) item).makeShortcut()); mLauncher.showWorkspace(true, new Runnable() { - @Override public void run() { mLauncher.getModel().addAndBindAddedWorkspaceApps( mLauncher, addShortcuts, screenProvider, 0, true); + announceConfirmation(R.string.item_added_to_workspace); } }); return true; } else if (item instanceof PendingAddItemInfo) { mLauncher.getModel().addAndBindPendingItem( mLauncher, (PendingAddItemInfo) item, screenProvider, 0); + announceConfirmation(R.string.item_added_to_workspace); return true; } } return false; } + + private void announceConfirmation(int resId) { + announceConfirmation(mLauncher.getResources().getString(resId)); + } + + private void announceConfirmation(String confirmation) { + mLauncher.getDragLayer().announceForAccessibility(confirmation); + + } + + public boolean isInAccessibleDrag() { + return mDragInfo != null; + } + + public DragInfo getDragInfo() { + return mDragInfo; + } + + public void handleAccessibleDrop(CellLayout targetContainer, Rect dropLocation, + String confirmation) { + if (!isInAccessibleDrag()) return; + + int[] loc = new int[2]; + loc[0] = dropLocation.centerX(); + loc[1] = dropLocation.centerY(); + + mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(targetContainer, loc); + mLauncher.getDragController().completeAccessibleDrag(loc); + + endAccessibleDrag(); + announceConfirmation(confirmation); + } + + public void beginAccessibleDrag(View item, ItemInfo info) { + mDragInfo = new DragInfo(); + mDragInfo.info = info; + mDragInfo.item = item; + mDragInfo.dragType = DragType.ICON; + if (info instanceof FolderInfo) { + mDragInfo.dragType = DragType.FOLDER; + } else if (info instanceof LauncherAppWidgetInfo) { + mDragInfo.dragType = DragType.WIDGET; + } + + CellLayout.CellInfo cellInfo = new CellLayout.CellInfo(item, info); + + 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); + } + + public boolean onBackPressed() { + if (isInAccessibleDrag()) { + cancelAccessibleDrag(); + return true; + } + return false; + } + + private void cancelAccessibleDrag() { + mLauncher.getDragController().cancelDrag(); + endAccessibleDrag(); + } + + private void endAccessibleDrag() { + mDragInfo = null; + mLauncher.getWorkspace().enableAccessibleDrag(false); + } } diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index 8e6557f73..d8896ccd2 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -62,7 +62,7 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks { private static LauncherAppState INSTANCE; private DynamicGrid mDynamicGrid; - private AccessibilityDelegate mAccessibilityDelegate; + private LauncherAccessibilityDelegate mAccessibilityDelegate; public static LauncherAppState getInstance() { if (INSTANCE == null) { @@ -168,7 +168,7 @@ public class LauncherAppState implements DeviceProfile.DeviceProfileCallbacks { return mModel; } - AccessibilityDelegate getAccessibilityDelegate() { + LauncherAccessibilityDelegate getAccessibilityDelegate() { return mAccessibilityDelegate; } diff --git a/src/com/android/launcher3/SearchDropTargetBar.java b/src/com/android/launcher3/SearchDropTargetBar.java index 99c2e0859..cc17820ff 100644 --- a/src/com/android/launcher3/SearchDropTargetBar.java +++ b/src/com/android/launcher3/SearchDropTargetBar.java @@ -197,6 +197,10 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D */ @Override public void onDragStart(DragSource source, Object info, int dragAction) { + showDeleteTarget(); + } + + public void showDeleteTarget() { // Animate out the QSB search bar, and animate in the drop target bar prepareStartAnimation(mDropTargetBar); mDropTargetBarAnim.start(); @@ -206,6 +210,16 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D } } + public void hideDeleteTarget() { + // Restore the QSB search bar, and animate out the drop target bar + prepareStartAnimation(mDropTargetBar); + mDropTargetBarAnim.reverse(); + if (!mIsSearchBarHidden) { + prepareStartAnimation(mQSBSearchBar); + mQSBSearchBarAnim.reverse(); + } + } + public void deferOnDragEnd() { mDeferOnDragEnd = true; } @@ -213,13 +227,7 @@ public class SearchDropTargetBar extends FrameLayout implements DragController.D @Override public void onDragEnd() { if (!mDeferOnDragEnd) { - // Restore the QSB search bar, and animate out the drop target bar - prepareStartAnimation(mDropTargetBar); - mDropTargetBarAnim.reverse(); - if (!mIsSearchBarHidden) { - prepareStartAnimation(mQSBSearchBar); - mQSBSearchBarAnim.reverse(); - } + hideDeleteTarget(); } else { mDeferOnDragEnd = false; } diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index b9c1f4d3d..125a2ed50 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -26,6 +26,7 @@ import android.animation.PropertyValuesHolder; import android.animation.TimeInterpolator; import android.animation.ValueAnimator; import android.animation.ValueAnimator.AnimatorUpdateListener; +import android.annotation.TargetApi; import android.app.WallpaperManager; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; @@ -44,6 +45,7 @@ import android.graphics.Rect; import android.graphics.Region.Op; import android.graphics.drawable.Drawable; import android.os.AsyncTask; +import android.os.Build; import android.os.Handler; import android.os.IBinder; import android.os.Parcelable; @@ -572,6 +574,10 @@ public class Workspace extends SmoothPagedView mWorkspaceScreens.put(screenId, newScreen); mScreenOrder.add(insertIndex, screenId); addView(newScreen, insertIndex); + + if (LauncherAppState.getInstance().getAccessibilityDelegate().isInAccessibleDrag()) { + newScreen.enableAccessibleDrag(true); + } return screenId; } @@ -1621,7 +1627,6 @@ public class Workspace extends SmoothPagedView float scrollProgress = getScrollProgress(screenCenter, child, i); float alpha = 1 - Math.abs(scrollProgress); child.getShortcutsAndWidgets().setAlpha(alpha); - //child.setBackgroundAlphaMultiplier(1 - alpha); } } } @@ -1634,6 +1639,23 @@ public class Workspace extends SmoothPagedView } } + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public void enableAccessibleDrag(boolean enable) { + for (int i = 0; i < getChildCount(); i++) { + CellLayout child = (CellLayout) getChildAt(i); + child.enableAccessibleDrag(enable); + } + + if (enable) { + // We need to allow our individual children to become click handlers in this case + setOnClickListener(null); + } else { + // Reset our click listener + setOnClickListener(mLauncher); + } + mLauncher.getHotseat().getLayout().enableAccessibleDrag(enable); + } + public boolean hasCustomContent() { return (mScreenOrder.size() > 0 && mScreenOrder.get(0) == CUSTOM_CONTENT_SCREEN_ID); } @@ -2184,7 +2206,7 @@ public class Workspace extends SmoothPagedView private void updateAccessibilityFlags() { int accessible = mState == State.NORMAL ? - IMPORTANT_FOR_ACCESSIBILITY_YES : + IMPORTANT_FOR_ACCESSIBILITY_NO : IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS; setImportantForAccessibility(accessible); } @@ -2674,7 +2696,11 @@ public class Workspace extends SmoothPagedView return b; } - void startDrag(CellLayout.CellInfo cellInfo) { + public void startDrag(CellLayout.CellInfo cellInfo) { + startDrag(cellInfo, false); + } + + public void startDrag(CellLayout.CellInfo cellInfo, boolean accessible) { View child = cellInfo.cell; // Make sure the drag was started by a long press as opposed to a long click. @@ -2687,10 +2713,14 @@ public class Workspace extends SmoothPagedView CellLayout layout = (CellLayout) child.getParent().getParent(); layout.prepareChildForDrag(child); - beginDragShared(child, this); + beginDragShared(child, this, accessible); } public void beginDragShared(View child, DragSource source) { + beginDragShared(child, source, false); + } + + public void beginDragShared(View child, DragSource source, boolean accessible) { child.clearFocus(); child.setPressed(false); @@ -2744,7 +2774,7 @@ public class Workspace extends SmoothPagedView } DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), - DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale); + DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, accessible); dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor()); if (child.getParent() instanceof ShortcutAndWidgetContainer) { @@ -2794,7 +2824,7 @@ public class Workspace extends SmoothPagedView // Start the drag DragView dv = mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(), - DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale); + DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect, scale, false); dv.setIntrinsicIconScaleFactor(source.getIntrinsicIconScaleFactor()); // Recycle temporary bitmaps @@ -3149,7 +3179,8 @@ public class Workspace extends SmoothPagedView final LauncherAppWidgetHostView hostView = (LauncherAppWidgetHostView) cell; AppWidgetProviderInfo pInfo = hostView.getAppWidgetInfo(); - if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE) { + if (pInfo != null && pInfo.resizeMode != AppWidgetProviderInfo.RESIZE_NONE + && !d.accessibleDrag) { final Runnable addResizeFrame = new Runnable() { public void run() { DragLayer dragLayer = mLauncher.getDragLayer(); @@ -3638,7 +3669,7 @@ public class Workspace extends SmoothPagedView mTargetCell[1]); manageFolderFeedback(info, mDragTargetLayout, mTargetCell, - targetCellDistance, dragOverView); + targetCellDistance, dragOverView, d.accessibleDrag); boolean nearestDropOccupied = mDragTargetLayout.isNearestDropLocationOccupied((int) mDragViewVisualCenter[0], (int) mDragViewVisualCenter[1], item.spanX, @@ -3676,15 +3707,21 @@ public class Workspace extends SmoothPagedView } private void manageFolderFeedback(ItemInfo info, CellLayout targetLayout, - int[] targetCell, float distance, View dragOverView) { + int[] targetCell, float distance, View dragOverView, boolean accessibleDrag) { boolean userFolderPending = willCreateUserFolder(info, targetLayout, targetCell, distance, false); - if (mDragMode == DRAG_MODE_NONE && userFolderPending && !mFolderCreationAlarm.alarmPending()) { - mFolderCreationAlarm.setOnAlarmListener(new - FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1])); - mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT); + + FolderCreationAlarmListener listener = new + FolderCreationAlarmListener(targetLayout, targetCell[0], targetCell[1]); + + if (!accessibleDrag) { + mFolderCreationAlarm.setOnAlarmListener(listener); + mFolderCreationAlarm.setAlarm(FOLDER_CREATION_TIMEOUT); + } else { + listener.onAlarm(mFolderCreationAlarm); + } return; } |