OSDN Git Service

Merge "Import translations. DO NOT MERGE" into ub-launcher3-dorval
[android-x86/packages-apps-Launcher3.git] / src / com / android / launcher3 / accessibility / LauncherAccessibilityDelegate.java
1 package com.android.launcher3.accessibility;
2
3 import android.app.AlertDialog;
4 import android.appwidget.AppWidgetProviderInfo;
5 import android.content.DialogInterface;
6 import android.graphics.Rect;
7 import android.os.Bundle;
8 import android.os.Handler;
9 import android.text.TextUtils;
10 import android.util.Log;
11 import android.util.SparseArray;
12 import android.view.View;
13 import android.view.View.AccessibilityDelegate;
14 import android.view.accessibility.AccessibilityNodeInfo;
15 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
16
17 import com.android.launcher3.AppInfo;
18 import com.android.launcher3.AppWidgetResizeFrame;
19 import com.android.launcher3.BubbleTextView;
20 import com.android.launcher3.CellLayout;
21 import com.android.launcher3.popup.PopupContainerWithArrow;
22 import com.android.launcher3.DeleteDropTarget;
23 import com.android.launcher3.DropTarget.DragObject;
24 import com.android.launcher3.FolderInfo;
25 import com.android.launcher3.InfoDropTarget;
26 import com.android.launcher3.ItemInfo;
27 import com.android.launcher3.Launcher;
28 import com.android.launcher3.LauncherAppWidgetHostView;
29 import com.android.launcher3.LauncherAppWidgetInfo;
30 import com.android.launcher3.LauncherModel;
31 import com.android.launcher3.LauncherSettings;
32 import com.android.launcher3.PendingAddItemInfo;
33 import com.android.launcher3.R;
34 import com.android.launcher3.ShortcutInfo;
35 import com.android.launcher3.UninstallDropTarget;
36 import com.android.launcher3.Workspace;
37 import com.android.launcher3.dragndrop.DragController.DragListener;
38 import com.android.launcher3.dragndrop.DragOptions;
39 import com.android.launcher3.folder.Folder;
40 import com.android.launcher3.shortcuts.DeepShortcutManager;
41 import com.android.launcher3.util.Thunk;
42
43 import java.util.ArrayList;
44
45 public class LauncherAccessibilityDelegate extends AccessibilityDelegate implements DragListener {
46
47     private static final String TAG = "LauncherAccessibilityDelegate";
48
49     protected static final int REMOVE = R.id.action_remove;
50     protected static final int INFO = R.id.action_info;
51     protected static final int UNINSTALL = R.id.action_uninstall;
52     protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
53     protected static final int MOVE = R.id.action_move;
54     protected static final int MOVE_TO_WORKSPACE = R.id.action_move_to_workspace;
55     protected static final int RESIZE = R.id.action_resize;
56     public static final int DEEP_SHORTCUTS = R.id.action_deep_shortcuts;
57
58     public enum DragType {
59         ICON,
60         FOLDER,
61         WIDGET
62     }
63
64     public static class DragInfo {
65         public DragType dragType;
66         public ItemInfo info;
67         public View item;
68     }
69
70     protected final SparseArray<AccessibilityAction> mActions = new SparseArray<>();
71     @Thunk final Launcher mLauncher;
72
73     private DragInfo mDragInfo = null;
74
75     public LauncherAccessibilityDelegate(Launcher launcher) {
76         mLauncher = launcher;
77
78         mActions.put(REMOVE, new AccessibilityAction(REMOVE,
79                 launcher.getText(R.string.remove_drop_target_label)));
80         mActions.put(INFO, new AccessibilityAction(INFO,
81                 launcher.getText(R.string.app_info_drop_target_label)));
82         mActions.put(UNINSTALL, new AccessibilityAction(UNINSTALL,
83                 launcher.getText(R.string.uninstall_drop_target_label)));
84         mActions.put(ADD_TO_WORKSPACE, new AccessibilityAction(ADD_TO_WORKSPACE,
85                 launcher.getText(R.string.action_add_to_workspace)));
86         mActions.put(MOVE, new AccessibilityAction(MOVE,
87                 launcher.getText(R.string.action_move)));
88         mActions.put(MOVE_TO_WORKSPACE, new AccessibilityAction(MOVE_TO_WORKSPACE,
89                 launcher.getText(R.string.action_move_to_workspace)));
90         mActions.put(RESIZE, new AccessibilityAction(RESIZE,
91                         launcher.getText(R.string.action_resize)));
92         mActions.put(DEEP_SHORTCUTS, new AccessibilityAction(DEEP_SHORTCUTS,
93                 launcher.getText(R.string.action_deep_shortcut)));
94     }
95
96     @Override
97     public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
98         super.onInitializeAccessibilityNodeInfo(host, info);
99         addSupportedActions(host, info, false);
100     }
101
102     public void addSupportedActions(View host, AccessibilityNodeInfo info, boolean fromKeyboard) {
103         if (!(host.getTag() instanceof ItemInfo)) return;
104         ItemInfo item = (ItemInfo) host.getTag();
105
106         // If the request came from keyboard, do not add custom shortcuts as that is already
107         // exposed as a direct shortcut
108         if (!fromKeyboard && DeepShortcutManager.supportsShortcuts(item)) {
109             info.addAction(mActions.get(DEEP_SHORTCUTS));
110         }
111
112         if (DeleteDropTarget.supportsAccessibleDrop(item)) {
113             info.addAction(mActions.get(REMOVE));
114         }
115         if (UninstallDropTarget.supportsDrop(host.getContext(), item)) {
116             info.addAction(mActions.get(UNINSTALL));
117         }
118         if (InfoDropTarget.supportsDrop(host.getContext(), item)) {
119             info.addAction(mActions.get(INFO));
120         }
121
122         // Do not add move actions for keyboard request as this uses virtual nodes.
123         if (!fromKeyboard && ((item instanceof ShortcutInfo)
124                 || (item instanceof LauncherAppWidgetInfo)
125                 || (item instanceof FolderInfo))) {
126             info.addAction(mActions.get(MOVE));
127
128             if (item.container >= 0) {
129                 info.addAction(mActions.get(MOVE_TO_WORKSPACE));
130             } else if (item instanceof LauncherAppWidgetInfo) {
131                 if (!getSupportedResizeActions(host, (LauncherAppWidgetInfo) item).isEmpty()) {
132                     info.addAction(mActions.get(RESIZE));
133                 }
134             }
135         }
136
137         if ((item instanceof AppInfo) || (item instanceof PendingAddItemInfo)) {
138             info.addAction(mActions.get(ADD_TO_WORKSPACE));
139         }
140     }
141
142     @Override
143     public boolean performAccessibilityAction(View host, int action, Bundle args) {
144         if ((host.getTag() instanceof ItemInfo)
145                 && performAction(host, (ItemInfo) host.getTag(), action)) {
146             return true;
147         }
148         return super.performAccessibilityAction(host, action, args);
149     }
150
151     public boolean performAction(final View host, final ItemInfo item, int action) {
152         if (action == REMOVE) {
153             DeleteDropTarget.removeWorkspaceOrFolderItem(mLauncher, item, host);
154             return true;
155         } else if (action == INFO) {
156             InfoDropTarget.startDetailsActivityForInfo(item, mLauncher, null);
157             return true;
158         } else if (action == UNINSTALL) {
159             return UninstallDropTarget.startUninstallActivity(mLauncher, item);
160         } else if (action == MOVE) {
161             beginAccessibleDrag(host, item);
162         } else if (action == ADD_TO_WORKSPACE) {
163             final int[] coordinates = new int[2];
164             final long screenId = findSpaceOnWorkspace(item, coordinates);
165             mLauncher.showWorkspace(true, new Runnable() {
166
167                 @Override
168                 public void run() {
169                     if (item instanceof AppInfo) {
170                         ShortcutInfo info = ((AppInfo) item).makeShortcut();
171                         mLauncher.getModelWriter().addItemToDatabase(info,
172                                 LauncherSettings.Favorites.CONTAINER_DESKTOP,
173                                 screenId, coordinates[0], coordinates[1]);
174
175                         ArrayList<ItemInfo> itemList = new ArrayList<>();
176                         itemList.add(info);
177                         mLauncher.bindItems(itemList, 0, itemList.size(), true);
178                     } else if (item instanceof PendingAddItemInfo) {
179                         PendingAddItemInfo info = (PendingAddItemInfo) item;
180                         Workspace workspace = mLauncher.getWorkspace();
181                         workspace.snapToPage(workspace.getPageIndexForScreenId(screenId));
182                         mLauncher.addPendingItem(info, LauncherSettings.Favorites.CONTAINER_DESKTOP,
183                                 screenId, coordinates, info.spanX, info.spanY);
184                     }
185                     announceConfirmation(R.string.item_added_to_workspace);
186                 }
187             });
188             return true;
189         } else if (action == MOVE_TO_WORKSPACE) {
190             Folder folder = Folder.getOpen(mLauncher);
191             folder.close(true);
192             ShortcutInfo info = (ShortcutInfo) item;
193             folder.getInfo().remove(info, false);
194
195             final int[] coordinates = new int[2];
196             final long screenId = findSpaceOnWorkspace(item, coordinates);
197             mLauncher.getModelWriter().moveItemInDatabase(info,
198                     LauncherSettings.Favorites.CONTAINER_DESKTOP,
199                     screenId, coordinates[0], coordinates[1]);
200
201             // Bind the item in next frame so that if a new workspace page was created,
202             // it will get laid out.
203             new Handler().post(new Runnable() {
204
205                 @Override
206                 public void run() {
207                     ArrayList<ItemInfo> itemList = new ArrayList<>();
208                     itemList.add(item);
209                     mLauncher.bindItems(itemList, 0, itemList.size(), true);
210                     announceConfirmation(R.string.item_moved);
211                 }
212             });
213         } else if (action == RESIZE) {
214             final LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) item;
215             final ArrayList<Integer> actions = getSupportedResizeActions(host, info);
216             CharSequence[] labels = new CharSequence[actions.size()];
217             for (int i = 0; i < actions.size(); i++) {
218                 labels[i] = mLauncher.getText(actions.get(i));
219             }
220
221             new AlertDialog.Builder(mLauncher)
222                 .setTitle(R.string.action_resize)
223                 .setItems(labels, new DialogInterface.OnClickListener() {
224
225                     @Override
226                     public void onClick(DialogInterface dialog, int which) {
227                         performResizeAction(actions.get(which), host, info);
228                         dialog.dismiss();
229                     }
230                 })
231                 .show();
232             return true;
233         } else if (action == DEEP_SHORTCUTS) {
234             return PopupContainerWithArrow.showForIcon((BubbleTextView) host) != null;
235         }
236         return false;
237     }
238
239     private ArrayList<Integer> getSupportedResizeActions(View host, LauncherAppWidgetInfo info) {
240         ArrayList<Integer> actions = new ArrayList<>();
241
242         AppWidgetProviderInfo providerInfo = ((LauncherAppWidgetHostView) host).getAppWidgetInfo();
243         if (providerInfo == null) {
244             return actions;
245         }
246
247         CellLayout layout = (CellLayout) host.getParent().getParent();
248         if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0) {
249             if (layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY) ||
250                     layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY)) {
251                 actions.add(R.string.action_increase_width);
252             }
253
254             if (info.spanX > info.minSpanX && info.spanX > 1) {
255                 actions.add(R.string.action_decrease_width);
256             }
257         }
258
259         if ((providerInfo.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0) {
260             if (layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1) ||
261                     layout.isRegionVacant(info.cellX, info.cellY - 1, info.spanX, 1)) {
262                 actions.add(R.string.action_increase_height);
263             }
264
265             if (info.spanY > info.minSpanY && info.spanY > 1) {
266                 actions.add(R.string.action_decrease_height);
267             }
268         }
269         return actions;
270     }
271
272     @Thunk void performResizeAction(int action, View host, LauncherAppWidgetInfo info) {
273         CellLayout.LayoutParams lp = (CellLayout.LayoutParams) host.getLayoutParams();
274         CellLayout layout = (CellLayout) host.getParent().getParent();
275         layout.markCellsAsUnoccupiedForView(host);
276
277         if (action == R.string.action_increase_width) {
278             if (((host.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL)
279                     && layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY))
280                     || !layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY)) {
281                 lp.cellX --;
282                 info.cellX --;
283             }
284             lp.cellHSpan ++;
285             info.spanX ++;
286         } else if (action == R.string.action_decrease_width) {
287             lp.cellHSpan --;
288             info.spanX --;
289         } else if (action == R.string.action_increase_height) {
290             if (!layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1)) {
291                 lp.cellY --;
292                 info.cellY --;
293             }
294             lp.cellVSpan ++;
295             info.spanY ++;
296         } else if (action == R.string.action_decrease_height) {
297             lp.cellVSpan --;
298             info.spanY --;
299         }
300
301         layout.markCellsAsOccupiedForView(host);
302         Rect sizeRange = new Rect();
303         AppWidgetResizeFrame.getWidgetSizeRanges(mLauncher, info.spanX, info.spanY, sizeRange);
304         ((LauncherAppWidgetHostView) host).updateAppWidgetSize(null,
305                 sizeRange.left, sizeRange.top, sizeRange.right, sizeRange.bottom);
306         host.requestLayout();
307         mLauncher.getModelWriter().updateItemInDatabase(info);
308         announceConfirmation(mLauncher.getString(R.string.widget_resized, info.spanX, info.spanY));
309     }
310
311     @Thunk void announceConfirmation(int resId) {
312         announceConfirmation(mLauncher.getResources().getString(resId));
313     }
314
315     @Thunk void announceConfirmation(String confirmation) {
316         mLauncher.getDragLayer().announceForAccessibility(confirmation);
317
318     }
319
320     public boolean isInAccessibleDrag() {
321         return mDragInfo != null;
322     }
323
324     public DragInfo getDragInfo() {
325         return mDragInfo;
326     }
327
328     /**
329      * @param clickedTarget the actual view that was clicked
330      * @param dropLocation relative to {@param clickedTarget}. If provided, its center is used
331      * as the actual drop location otherwise the views center is used.
332      */
333     public void handleAccessibleDrop(View clickedTarget, Rect dropLocation,
334             String confirmation) {
335         if (!isInAccessibleDrag()) return;
336
337         int[] loc = new int[2];
338         if (dropLocation == null) {
339             loc[0] = clickedTarget.getWidth() / 2;
340             loc[1] = clickedTarget.getHeight() / 2;
341         } else {
342             loc[0] = dropLocation.centerX();
343             loc[1] = dropLocation.centerY();
344         }
345
346         mLauncher.getDragLayer().getDescendantCoordRelativeToSelf(clickedTarget, loc);
347         mLauncher.getDragController().completeAccessibleDrag(loc);
348
349         if (!TextUtils.isEmpty(confirmation)) {
350             announceConfirmation(confirmation);
351         }
352     }
353
354     public void beginAccessibleDrag(View item, ItemInfo info) {
355         mDragInfo = new DragInfo();
356         mDragInfo.info = info;
357         mDragInfo.item = item;
358         mDragInfo.dragType = DragType.ICON;
359         if (info instanceof FolderInfo) {
360             mDragInfo.dragType = DragType.FOLDER;
361         } else if (info instanceof LauncherAppWidgetInfo) {
362             mDragInfo.dragType = DragType.WIDGET;
363         }
364
365         CellLayout.CellInfo cellInfo = new CellLayout.CellInfo(item, info);
366
367         Rect pos = new Rect();
368         mLauncher.getDragLayer().getDescendantRectRelativeToSelf(item, pos);
369         mLauncher.getDragController().prepareAccessibleDrag(pos.centerX(), pos.centerY());
370
371         Folder folder = Folder.getOpen(mLauncher);
372         if (folder != null) {
373             if (!folder.getItemsInReadingOrder().contains(item)) {
374                 folder.close(true);
375                 folder = null;
376             }
377         }
378
379         mLauncher.getDragController().addDragListener(this);
380
381         DragOptions options = new DragOptions();
382         options.isAccessibleDrag = true;
383         if (folder != null) {
384             folder.startDrag(cellInfo.cell, options);
385         } else {
386             mLauncher.getWorkspace().startDrag(cellInfo, options);
387         }
388     }
389
390     @Override
391     public void onDragStart(DragObject dragObject, DragOptions options) {
392         // No-op
393     }
394
395     @Override
396     public void onDragEnd() {
397         mLauncher.getDragController().removeDragListener(this);
398         mDragInfo = null;
399     }
400
401     /**
402      * Find empty space on the workspace and returns the screenId.
403      */
404     protected long findSpaceOnWorkspace(ItemInfo info, int[] outCoordinates) {
405         Workspace workspace = mLauncher.getWorkspace();
406         ArrayList<Long> workspaceScreens = workspace.getScreenOrder();
407         long screenId;
408
409         // First check if there is space on the current screen.
410         int screenIndex = workspace.getCurrentPage();
411         screenId = workspaceScreens.get(screenIndex);
412         CellLayout layout = (CellLayout) workspace.getPageAt(screenIndex);
413
414         boolean found = layout.findCellForSpan(outCoordinates, info.spanX, info.spanY);
415         screenIndex = workspace.hasCustomContent() ? 1 : 0;
416         while (!found && screenIndex < workspaceScreens.size()) {
417             screenId = workspaceScreens.get(screenIndex);
418             layout = (CellLayout) workspace.getPageAt(screenIndex);
419             found = layout.findCellForSpan(outCoordinates, info.spanX, info.spanY);
420             screenIndex++;
421         }
422
423         if (found) {
424             return screenId;
425         }
426
427         workspace.addExtraEmptyScreen();
428         screenId = workspace.commitExtraEmptyScreen();
429         layout = workspace.getScreenWithId(screenId);
430         found = layout.findCellForSpan(outCoordinates, info.spanX, info.spanY);
431
432         if (!found) {
433             Log.wtf(TAG, "Not enough space on an empty screen");
434         }
435         return screenId;
436     }
437 }