Merge "Enabling accessibility drag and drop in folder" into ub-launcher3-burnaby

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