Merge "Enabling accessible drag and drop" into ub-launcher3-burnaby
This commit is contained in:
@@ -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) \
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -616,6 +634,34 @@ public class DragController {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user