• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.launcher3.accessibility;
17 
18 import android.content.Context;
19 import android.graphics.Rect;
20 import android.os.Bundle;
21 import android.text.TextUtils;
22 import android.util.SparseArray;
23 import android.view.View;
24 import android.view.accessibility.AccessibilityNodeInfo;
25 
26 import com.android.launcher3.BubbleTextView;
27 import com.android.launcher3.DropTarget;
28 import com.android.launcher3.LauncherSettings;
29 import com.android.launcher3.dragndrop.DragController;
30 import com.android.launcher3.dragndrop.DragOptions;
31 import com.android.launcher3.model.data.FolderInfo;
32 import com.android.launcher3.model.data.ItemInfo;
33 import com.android.launcher3.model.data.LauncherAppWidgetInfo;
34 import com.android.launcher3.model.data.WorkspaceItemInfo;
35 import com.android.launcher3.util.Thunk;
36 import com.android.launcher3.views.ActivityContext;
37 import com.android.launcher3.views.BubbleTextHolder;
38 
39 import java.util.ArrayList;
40 import java.util.List;
41 
42 public abstract class BaseAccessibilityDelegate<T extends Context & ActivityContext>
43         extends View.AccessibilityDelegate implements DragController.DragListener {
44 
45     public enum DragType {
46         ICON,
47         FOLDER,
48         WIDGET
49     }
50 
51     public static class DragInfo {
52         public DragType dragType;
53         public ItemInfo info;
54         public View item;
55     }
56 
57     protected final SparseArray<LauncherAction> mActions = new SparseArray<>();
58     protected final T mContext;
59 
60     protected DragInfo mDragInfo = null;
61 
BaseAccessibilityDelegate(T context)62     protected BaseAccessibilityDelegate(T context) {
63         mContext = context;
64     }
65 
66     @Override
onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info)67     public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
68         super.onInitializeAccessibilityNodeInfo(host, info);
69         if (host.getTag() instanceof ItemInfo) {
70             ItemInfo item = (ItemInfo) host.getTag();
71 
72             List<LauncherAction> actions = new ArrayList<>();
73             getSupportedActions(host, item, actions);
74             actions.forEach(la -> info.addAction(la.accessibilityAction));
75 
76             if (!itemSupportsLongClick(host)) {
77                 info.setLongClickable(false);
78                 info.removeAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_LONG_CLICK);
79             }
80         }
81     }
82 
83     /**
84      * Adds all the accessibility actions that can be handled.
85      */
getSupportedActions(View host, ItemInfo item, List<LauncherAction> out)86     protected abstract void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out);
87 
itemSupportsLongClick(View host)88     private boolean itemSupportsLongClick(View host) {
89         if (host instanceof BubbleTextView) {
90             return ((BubbleTextView) host).canShowLongPressPopup();
91         } else if (host instanceof BubbleTextHolder) {
92             BubbleTextHolder holder = (BubbleTextHolder) host;
93             return holder.getBubbleText() != null && holder.getBubbleText().canShowLongPressPopup();
94         } else {
95             return false;
96         }
97     }
98 
itemSupportsAccessibleDrag(ItemInfo item)99     protected boolean itemSupportsAccessibleDrag(ItemInfo item) {
100         if (item instanceof WorkspaceItemInfo) {
101             // Support the action unless the item is in a context menu.
102             return item.screenId >= 0
103                     && item.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
104         }
105         return (item instanceof LauncherAppWidgetInfo)
106                 || (item instanceof FolderInfo);
107     }
108 
109     @Override
performAccessibilityAction(View host, int action, Bundle args)110     public boolean performAccessibilityAction(View host, int action, Bundle args) {
111         if ((host.getTag() instanceof ItemInfo)
112                 && performAction(host, (ItemInfo) host.getTag(), action, false)) {
113             return true;
114         }
115         return super.performAccessibilityAction(host, action, args);
116     }
117 
performAction( View host, ItemInfo item, int action, boolean fromKeyboard)118     protected abstract boolean performAction(
119             View host, ItemInfo item, int action, boolean fromKeyboard);
120 
121     @Thunk
announceConfirmation(String confirmation)122     protected void announceConfirmation(String confirmation) {
123         mContext.getDragLayer().announceForAccessibility(confirmation);
124     }
125 
isInAccessibleDrag()126     public boolean isInAccessibleDrag() {
127         return mDragInfo != null;
128     }
129 
getDragInfo()130     public DragInfo getDragInfo() {
131         return mDragInfo;
132     }
133 
134     /**
135      * @param clickedTarget the actual view that was clicked
136      * @param dropLocation relative to {@param clickedTarget}. If provided, its center is used
137      * as the actual drop location otherwise the views center is used.
138      */
handleAccessibleDrop(View clickedTarget, Rect dropLocation, String confirmation)139     public void handleAccessibleDrop(View clickedTarget, Rect dropLocation,
140             String confirmation) {
141         if (!isInAccessibleDrag()) return;
142 
143         int[] loc = new int[2];
144         if (dropLocation == null) {
145             loc[0] = clickedTarget.getWidth() / 2;
146             loc[1] = clickedTarget.getHeight() / 2;
147         } else {
148             loc[0] = dropLocation.centerX();
149             loc[1] = dropLocation.centerY();
150         }
151 
152         mContext.getDragLayer().getDescendantCoordRelativeToSelf(clickedTarget, loc);
153         mContext.getDragController().completeAccessibleDrag(loc);
154 
155         if (!TextUtils.isEmpty(confirmation)) {
156             announceConfirmation(confirmation);
157         }
158     }
159 
beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard)160     protected abstract boolean beginAccessibleDrag(View item, ItemInfo info, boolean fromKeyboard);
161 
162 
163     @Override
onDragEnd()164     public void onDragEnd() {
165         mContext.getDragController().removeDragListener(this);
166         mDragInfo = null;
167     }
168 
169     @Override
onDragStart(DropTarget.DragObject dragObject, DragOptions options)170     public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) {
171         // No-op
172     }
173 
174     public class LauncherAction {
175         public final int keyCode;
176         public final AccessibilityNodeInfo.AccessibilityAction accessibilityAction;
177 
178         private final BaseAccessibilityDelegate<T> mDelegate;
179 
LauncherAction(int id, int labelRes, int keyCode)180         public LauncherAction(int id, int labelRes, int keyCode) {
181             this.keyCode = keyCode;
182             accessibilityAction = new AccessibilityNodeInfo.AccessibilityAction(
183                     id, mContext.getString(labelRes));
184             mDelegate = BaseAccessibilityDelegate.this;
185         }
186 
187         /**
188          * Invokes the action for the provided host
189          */
invokeFromKeyboard(View host)190         public boolean invokeFromKeyboard(View host) {
191             if (host != null && host.getTag() instanceof ItemInfo) {
192                 return mDelegate.performAction(
193                         host, (ItemInfo) host.getTag(), accessibilityAction.getId(), true);
194             } else {
195                 return false;
196             }
197         }
198     }
199 }
200