summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/accessibility
diff options
context:
space:
mode:
authorHyunyoung Song <hyunyoungs@google.com>2015-07-21 17:01:26 -0700
committerEd Heyl <edheyl@google.com>2015-07-22 11:31:04 -0700
commite612775922ec9f8cc4e5cb976bc62b3312a3de0e (patch)
tree96beaf07307486f6491f022da63ebe381765d1a8 /src/com/android/launcher3/accessibility
parenta83129f0bc778fd1ccd799bd8cfa40270f632e1d (diff)
downloadandroid_packages_apps_Trebuchet-e612775922ec9f8cc4e5cb976bc62b3312a3de0e.tar.gz
android_packages_apps_Trebuchet-e612775922ec9f8cc4e5cb976bc62b3312a3de0e.tar.bz2
android_packages_apps_Trebuchet-e612775922ec9f8cc4e5cb976bc62b3312a3de0e.zip
resolved conflicts for merge of 13ef17a3 to mnc-dr-dev
b/22609402 Change-Id: I140cf972d57e14737a6f91c0b4a8ec6c7ff1af2b
Diffstat (limited to 'src/com/android/launcher3/accessibility')
-rw-r--r--src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java136
-rw-r--r--src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java56
-rw-r--r--src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java434
-rw-r--r--src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java95
-rw-r--r--src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java166
5 files changed, 887 insertions, 0 deletions
diff --git a/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
new file mode 100644
index 000000000..78accf720
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/DragAndDropAccessibilityDelegate.java
@@ -0,0 +1,136 @@
+/*
+ * 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.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;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
new file mode 100644
index 000000000..ff9989036
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/FolderAccessibilityHelper.java
@@ -0,0 +1,56 @@
+/*
+ * 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 {
+
+ /**
+ * 0-index position for the first cell in {@link #mView} in {@link #mParent}.
+ */
+ private final int mStartPosition;
+
+ private final FolderPagedView mParent;
+
+ public FolderAccessibilityHelper(CellLayout layout) {
+ super(layout);
+ mParent = (FolderPagedView) layout.getParent();
+
+ int index = mParent.indexOfChild(layout);
+ mStartPosition = index * layout.getCountX() * layout.getCountY();
+ }
+ @Override
+ protected int intersectsValidDropTarget(int id) {
+ return Math.min(id, mParent.getAllocatedContentSize() - mStartPosition - 1);
+ }
+
+ @Override
+ protected String getLocationDescriptionForIconDrop(int id) {
+ return mContext.getString(R.string.move_to_position, id + mStartPosition + 1);
+ }
+
+ @Override
+ protected String getConfirmationForIconDrop(int id) {
+ return mContext.getString(R.string.item_moved);
+ }
+}
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
new file mode 100644
index 000000000..fe7b25edd
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -0,0 +1,434 @@
+package com.android.launcher3.accessibility;
+
+import android.annotation.TargetApi;
+import android.app.AlertDialog;
+import android.appwidget.AppWidgetProviderInfo;
+import android.content.DialogInterface;
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.AppWidgetResizeFrame;
+import com.android.launcher3.CellLayout;
+import com.android.launcher3.DeleteDropTarget;
+import com.android.launcher3.DragController.DragListener;
+import com.android.launcher3.DragSource;
+import com.android.launcher3.Folder;
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.InfoDropTarget;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherAppWidgetHostView;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.PendingAddItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.UninstallDropTarget;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.util.Thunk;
+
+import java.util.ArrayList;
+
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class LauncherAccessibilityDelegate extends AccessibilityDelegate implements DragListener {
+
+ private static final String TAG = "LauncherAccessibilityDelegate";
+
+ private static final int REMOVE = R.id.action_remove;
+ private static final int INFO = R.id.action_info;
+ private static final int UNINSTALL = R.id.action_uninstall;
+ private static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
+ private static final int MOVE = R.id.action_move;
+ private static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
+ private static final int RESIZE = R.id.action_resize;
+
+ public enum DragType {
+ ICON,
+ FOLDER,
+ WIDGET
+ }
+
+ public static class DragInfo {
+ public DragType dragType;
+ public ItemInfo info;
+ public View item;
+ }
+
+ 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;
+
+ mActions.put(REMOVE, new AccessibilityAction(REMOVE,
+ launcher.getText(R.string.delete_target_label)));
+ mActions.put(INFO, new AccessibilityAction(INFO,
+ launcher.getText(R.string.info_target_label)));
+ mActions.put(UNINSTALL, new AccessibilityAction(UNINSTALL,
+ 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)));
+ mActions.put(MOVE_TO_WORKSPACE, new AccessibilityAction(MOVE_TO_WORKSPACE,
+ launcher.getText(R.string.action_move_to_workspace)));
+ mActions.put(RESIZE, new AccessibilityAction(RESIZE,
+ launcher.getText(R.string.action_resize)));
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ if (!(host.getTag() instanceof ItemInfo)) return;
+ ItemInfo item = (ItemInfo) host.getTag();
+
+ if (DeleteDropTarget.supportsDrop(item)) {
+ info.addAction(mActions.get(REMOVE));
+ }
+ if (UninstallDropTarget.supportsDrop(host.getContext(), item)) {
+ info.addAction(mActions.get(UNINSTALL));
+ }
+ if (InfoDropTarget.supportsDrop(host.getContext(), item)) {
+ info.addAction(mActions.get(INFO));
+ }
+
+ if ((item instanceof ShortcutInfo)
+ || (item instanceof LauncherAppWidgetInfo)
+ || (item instanceof FolderInfo)) {
+ info.addAction(mActions.get(MOVE));
+
+ if (item.container >= 0) {
+ info.addAction(mActions.get(MOVE_TO_WORKSPACE));
+ } else if (item instanceof LauncherAppWidgetInfo) {
+ if (!getSupportedResizeActions(host, (LauncherAppWidgetInfo) item).isEmpty()) {
+ info.addAction(mActions.get(RESIZE));
+ }
+ }
+ } if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) {
+ info.addAction(mActions.get(ADD_TO_WORKSPACE));
+ }
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if ((host.getTag() instanceof ItemInfo)
+ && performAction(host, (ItemInfo) host.getTag(), action)) {
+ return true;
+ }
+ return super.performAccessibilityAction(host, action, args);
+ }
+
+ public boolean performAction(final View host, final ItemInfo item, int action) {
+ if (action == REMOVE) {
+ if (DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host)) {
+ announceConfirmation(R.string.item_removed);
+ return true;
+ }
+ return false;
+ } else if (action == INFO) {
+ InfoDropTarget.startDetailsActivityForInfo(item, mLauncher);
+ return true;
+ } else if (action == UNINSTALL) {
+ return UninstallDropTarget.startUninstallActivity(mLauncher, item);
+ } else if (action == MOVE) {
+ beginAccessibleDrag(host, item);
+ } else if (action == ADD_TO_WORKSPACE) {
+ final int[] coordinates = new int[2];
+ final long screenId = findSpaceOnWorkspace(item, coordinates);
+ mLauncher.showWorkspace(true, new Runnable() {
+
+ @Override
+ public void run() {
+ if (item instanceof AppInfo) {
+ ShortcutInfo info = ((AppInfo) item).makeShortcut();
+ LauncherModel.addItemToDatabase(mLauncher, info,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ screenId, coordinates[0], coordinates[1]);
+
+ ArrayList<ItemInfo> itemList = new ArrayList<>();
+ itemList.add(info);
+ mLauncher.bindItems(itemList, 0, itemList.size(), true);
+ } else if (item instanceof PendingAddItemInfo) {
+ PendingAddItemInfo info = (PendingAddItemInfo) item;
+ Workspace workspace = mLauncher.getWorkspace();
+ workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
+ mLauncher.addPendingItem(info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ screenId, coordinates, info.spanX, info.spanY);
+ }
+ announceConfirmation(R.string.item_added_to_workspace);
+ }
+ });
+ return true;
+ } else if (action == MOVE_TO_WORKSPACE) {
+ Folder folder = mLauncher.getWorkspace().getOpenFolder();
+ mLauncher.closeFolder(folder);
+ ShortcutInfo info = (ShortcutInfo) item;
+ folder.getInfo().remove(info);
+
+ final int[] coordinates = new int[2];
+ final long screenId = findSpaceOnWorkspace(item, coordinates);
+ LauncherModel.moveItemInDatabase(mLauncher, info,
+ LauncherSettings.Favorites.CONTAINER_DESKTOP,
+ screenId, coordinates[0], coordinates[1]);
+
+ // Bind the item in next frame so that if a new workspace page was created,
+ // it will get laid out.
+ new Handler().post(new Runnable() {
+
+ @Override
+ public void run() {
+ ArrayList<ItemInfo> itemList = new ArrayList<>();
+ itemList.add(item);
+ mLauncher.bindItems(itemList, 0, itemList.size(), true);
+ announceConfirmation(R.string.item_moved);
+ }
+ });
+ } else if (action == RESIZE) {
+ final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
+ final ArrayList<Integer> actions = getSupportedResizeActions(host, info);
+ CharSequence[] labels = new CharSequence[actions.size()];
+ for (int i = 0; i < actions.size(); i++) {
+ labels[i] = mLauncher.getText(actions.get(i));
+ }
+
+ new AlertDialog.Builder(mLauncher)
+ .setTitle(R.string.action_resize)
+ .setItems(labels, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ performResizeAction(actions.get(which), host, info);
+ dialog.dismiss();
+ }
+ })
+ .show();
+ }
+ return false;
+ }
+
+ private ArrayList<Integer> getSupportedResizeActions(View host, LauncherAppWidgetInfo info) {
+ ArrayList<Integer> actions = new ArrayList<>();
+
+ AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo();
+ if (providerInfo == null) {
+ return actions;
+ }
+
+ CellLayout layout = (CellLayout) host.getParent().getParent();
+ if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0) {
+ if (layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY) ||
+ layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY)) {
+ actions.add(R.string.action_increase_width);
+ }
+
+ if (info.spanX > info.minSpanX && info.spanX > 1) {
+ actions.add(R.string.action_decrease_width);
+ }
+ }
+
+ if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) {
+ if (layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1) ||
+ layout.isRegionVacant(info.cellX, info.cellY - 1, info.spanX, 1)) {
+ actions.add(R.string.action_increase_height);
+ }
+
+ if (info.spanY > info.minSpanY && info.spanY > 1) {
+ actions.add(R.string.action_decrease_height);
+ }
+ }
+ return actions;
+ }
+
+ @Thunk void performResizeAction(int action, View host, LauncherAppWidgetInfo info) {
+ CellLayout.LayoutParams lp = (CellLayout.LayoutParams) host.getLayoutParams();
+ CellLayout layout = (CellLayout) host.getParent().getParent();
+ layout.markCellsAsUnoccupiedForView(host);
+
+ if (action == R.string.action_increase_width) {
+ if (((host.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL)
+ && layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY))
+ || !layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY)) {
+ lp.cellX --;
+ info.cellX --;
+ }
+ lp.cellHSpan ++;
+ info.spanX ++;
+ } else if (action == R.string.action_decrease_width) {
+ lp.cellHSpan --;
+ info.spanX --;
+ } else if (action == R.string.action_increase_height) {
+ if (!layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1)) {
+ lp.cellY --;
+ info.cellY --;
+ }
+ lp.cellVSpan ++;
+ info.spanY ++;
+ } else if (action == R.string.action_decrease_height) {
+ lp.cellVSpan --;
+ info.spanY --;
+ }
+
+ layout.markCellsAsOccupiedForView(host);
+ Rect sizeRange = new Rect();
+ AppWidgetResizeFrame.getWidgetSizeRanges(mLauncher, info.spanX, info.spanY, sizeRange);
+ ((LauncherAppWidgetHostView) host).updateAppWidgetSize(null,
+ sizeRange.left, sizeRange.top, sizeRange.right, sizeRange.bottom);
+ host.requestLayout();
+ LauncherModel.updateItemInDatabase(mLauncher, info);
+ announceConfirmation(mLauncher.getString(R.string.widget_resized, info.spanX, info.spanY));
+ }
+
+ @Thunk void announceConfirmation(int resId) {
+ announceConfirmation(mLauncher.getResources().getString(resId));
+ }
+
+ @Thunk void announceConfirmation(String confirmation) {
+ mLauncher.getDragLayer().announceForAccessibility(confirmation);
+
+ }
+
+ public boolean isInAccessibleDrag() {
+ return mDragInfo != null;
+ }
+
+ public DragInfo getDragInfo() {
+ return mDragInfo;
+ }
+
+ /**
+ * @param clickedTarget the actual view that was clicked
+ * @param dropLocation relative to {@param clickedTarget}. If provided, its center is used
+ * as the actual drop location otherwise the views center is used.
+ */
+ public void handleAccessibleDrop(View clickedTarget, Rect dropLocation,
+ String confirmation) {
+ if (!isInAccessibleDrag()) return;
+
+ int[] loc = new int[2];
+ if (dropLocation == null) {
+ loc[0] = clickedTarget.getWidth() / 2;
+ loc[1] = clickedTarget.getHeight() / 2;
+ } else {
+ loc[0] = dropLocation.centerX();
+ loc[1] = dropLocation.centerY();
+ }
+
+ mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(clickedTarget, loc);
+ mLauncher.getDragController().completeAccessibleDrag(loc);
+
+ if (!TextUtils.isEmpty(confirmation)) {
+ 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());
+
+ 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);
+
+ if (mLauncher.getDragController().isDragging()) {
+ mLauncher.getDragController().addDragListener(this);
+ }
+ }
+
+
+ @Override
+ public void onDragStart(DragSource source, Object info, int dragAction) {
+ // No-op
+ }
+
+ @Override
+ public void onDragEnd() {
+ mLauncher.getDragController().removeDragListener(this);
+ mDragInfo = null;
+ if (mDragSource != null) {
+ mDragSource.enableAccessibleDrag(false);
+ mDragSource = null;
+ }
+ }
+
+ public static interface AccessibilityDragSource {
+ void startDrag(CellLayout.CellInfo cellInfo, boolean accessible);
+
+ void enableAccessibleDrag(boolean enable);
+ }
+
+ /**
+ * Find empty space on the workspace and returns the screenId.
+ */
+ private long findSpaceOnWorkspace(ItemInfo info, int[] outCoordinates) {
+ Workspace workspace = mLauncher.getWorkspace();
+ ArrayList<Long> workspaceScreens = workspace.getScreenOrder();
+ long screenId;
+
+ // First check if there is space on the current screen.
+ int screenIndex = workspace.getCurrentPage();
+ screenId = workspaceScreens.get(screenIndex);
+ CellLayout layout = (CellLayout) workspace.getPageAt(screenIndex);
+
+ boolean found = layout.findCellForSpan(outCoordinates, info.spanX, info.spanY);
+ screenIndex = workspace.hasCustomContent() ? 1 : 0;
+ while (!found && screenIndex < workspaceScreens.size()) {
+ screenId = workspaceScreens.get(screenIndex);
+ layout = (CellLayout) workspace.getPageAt(screenIndex);
+ found = layout.findCellForSpan(outCoordinates, info.spanX, info.spanY);
+ screenIndex++;
+ }
+
+ if (found) {
+ return screenId;
+ }
+
+ workspace.addExtraEmptyScreen();
+ screenId = workspace.commitExtraEmptyScreen();
+ layout = workspace.getScreenWithId(screenId);
+ found = layout.findCellForSpan(outCoordinates, info.spanX, info.spanY);
+
+ if (!found) {
+ Log.wtf(TAG, "Not enough space on an empty screen");
+ }
+ return screenId;
+ }
+}
diff --git a/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
new file mode 100644
index 000000000..c5b52de72
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/OverviewScreenAccessibilityDelegate.java
@@ -0,0 +1,95 @@
+/*
+ * 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.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.View.AccessibilityDelegate;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.Workspace;
+
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class OverviewScreenAccessibilityDelegate extends AccessibilityDelegate {
+
+ private static final int MOVE_BACKWARD = R.id.action_move_screen_backwards;
+ private static final int MOVE_FORWARD = R.id.action_move_screen_forwards;
+
+ private final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
+ private final Workspace mWorkspace;
+
+ public OverviewScreenAccessibilityDelegate(Workspace workspace) {
+ mWorkspace = workspace;
+
+ Context context = mWorkspace.getContext();
+ boolean isRtl = Utilities.isRtl(context.getResources());
+ mActions.put(MOVE_BACKWARD, new AccessibilityAction(MOVE_BACKWARD,
+ context.getText(isRtl ? R.string.action_move_screen_right :
+ R.string.action_move_screen_left)));
+ mActions.put(MOVE_FORWARD, new AccessibilityAction(MOVE_FORWARD,
+ context.getText(isRtl ? R.string.action_move_screen_left :
+ R.string.action_move_screen_right)));
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ if (host != null) {
+ if (action == AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS ) {
+ int index = mWorkspace.indexOfChild(host);
+ mWorkspace.setCurrentPage(index);
+ } else if (action == MOVE_FORWARD) {
+ movePage(mWorkspace.indexOfChild(host) + 1, host);
+ return true;
+ } else if (action == MOVE_BACKWARD) {
+ movePage(mWorkspace.indexOfChild(host) - 1, host);
+ return true;
+ }
+ }
+
+ return super.performAccessibilityAction(host, action, args);
+ }
+
+ private void movePage(int finalIndex, View view) {
+ mWorkspace.onStartReordering();
+ mWorkspace.removeView(view);
+ mWorkspace.addView(view, finalIndex);
+ mWorkspace.onEndReordering();
+ mWorkspace.announceForAccessibility(mWorkspace.getContext().getText(R.string.screen_moved));
+
+ mWorkspace.updateAccessibilityFlags();
+ view.performAccessibilityAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS, null);
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+
+ int index = mWorkspace.indexOfChild(host);
+ if (index < mWorkspace.getChildCount() - 1) {
+ info.addAction(mActions.get(MOVE_FORWARD));
+ }
+ if (index > mWorkspace.numCustomPages()) {
+ info.addAction(mActions.get(MOVE_BACKWARD));
+ }
+ }
+}
diff --git a/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
new file mode 100644
index 000000000..80ddc13b7
--- /dev/null
+++ b/src/com/android/launcher3/accessibility/WorkspaceAccessibilityHelper.java
@@ -0,0 +1,166 @@
+/*
+ * 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.accessibility.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)) {
+ // 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 "";
+ }
+}