summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Android.mk2
-rw-r--r--res/values/config.xml1
-rw-r--r--res/values/strings.xml28
-rw-r--r--src/com/android/launcher3/CellLayout.java306
-rw-r--r--src/com/android/launcher3/DragController.java56
-rw-r--r--src/com/android/launcher3/DropTarget.java3
-rw-r--r--src/com/android/launcher3/Launcher.java6
-rw-r--r--src/com/android/launcher3/LauncherAccessibilityDelegate.java107
-rw-r--r--src/com/android/launcher3/LauncherAppState.java4
-rw-r--r--src/com/android/launcher3/SearchDropTargetBar.java22
-rw-r--r--src/com/android/launcher3/Workspace.java63
11 files changed, 556 insertions, 42 deletions
diff --git a/Android.mk b/Android.mk
index 632dd0961..5267469b3 100644
--- a/Android.mk
+++ b/Android.mk
@@ -23,6 +23,8 @@ include $(CLEAR_VARS)
LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4
+
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(call all-java-files-under, WallpaperPicker/src) \
$(call all-renderscript-files-under, src) \
diff --git a/res/values/config.xml b/res/values/config.xml
index cbec51216..1acace6f9 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -104,4 +104,5 @@
<item type="id" name="action_uninstall" />
<item type="id" name="action_info" />
<item type="id" name="action_add_to_workspace" />
+ <item type="id" name="action_move" />
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8b7e6c199..74b88148c 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -305,4 +305,32 @@ s -->
<!-- Strings for accessibility actions -->
<!-- Accessibility action to add an app to workspace. [CHAR_LIMIT=30] -->
<string name="action_add_to_workspace">Add To Workspace</string>
+
+ <!-- Accessibility confirmation for item added to workspace -->
+ <string name="item_added_to_workspace">Item added to workspace</string>
+
+ <!-- Accessibility confirmation for item removed-->
+ <string name="item_removed_from_workspace">Item removed from workspace</string>
+
+ <!-- Accessibility action to move an item on the workspace. [CHAR_LIMIT=30] -->
+ <string name="action_move">Move Item</string>
+
+ <!-- Accessibility description to move item to empty cell. -->
+ <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>
+
+ <!-- Accessibility confirmation for item move -->
+ <string name="item_moved">Item moved</string>
+
+ <!-- Accessibility description to move item into an existing folder. -->
+ <string name="add_to_folder">Add to folder: <xliff:g id="name" example="Games">%1$s</xliff:g></string>
+
+ <!-- Accessibility confirmation for item added to folder-->
+ <string name="added_to_folder">Item added to folder</string>
+
+ <!-- Accessibility description to create folder with another item. -->
+ <string name="create_folder_with">Create folder with: <xliff:g id="name" example="Game">%1$s</xliff:g></string>
+
+ <!-- Accessibility confirmation for folder created -->
+ <string name="folder_created">Folder created</string>
+
</resources>
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;
}