diff options
author | Hyunyoung Song <hyunyoungs@google.com> | 2015-07-21 17:01:26 -0700 |
---|---|---|
committer | Ed Heyl <edheyl@google.com> | 2015-07-22 11:31:04 -0700 |
commit | e612775922ec9f8cc4e5cb976bc62b3312a3de0e (patch) | |
tree | 96beaf07307486f6491f022da63ebe381765d1a8 /src/com/android/launcher3/accessibility | |
parent | a83129f0bc778fd1ccd799bd8cfa40270f632e1d (diff) | |
download | android_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')
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 ""; + } +} |