1 /*
2  * Copyright (C) 2015 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 
17 package com.android.launcher3.accessibility;
18 
19 import android.content.Context;
20 import android.graphics.Rect;
21 import android.text.TextUtils;
22 import android.view.View;
23 
24 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
25 
26 import com.android.launcher3.CellLayout;
27 import com.android.launcher3.Launcher;
28 import com.android.launcher3.R;
29 import com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DragType;
30 import com.android.launcher3.dragndrop.DragLayer;
31 import com.android.launcher3.model.data.AppInfo;
32 import com.android.launcher3.model.data.FolderInfo;
33 import com.android.launcher3.model.data.ItemInfo;
34 import com.android.launcher3.model.data.WorkspaceItemInfo;
35 
36 /**
37  * Implementation of {@link DragAndDropAccessibilityDelegate} to support DnD on workspace.
38  */
39 public class WorkspaceAccessibilityHelper extends DragAndDropAccessibilityDelegate {
40 
41     private final Rect mTempRect = new Rect();
42     private final int[] mTempCords = new int[2];
43 
WorkspaceAccessibilityHelper(CellLayout layout)44     public WorkspaceAccessibilityHelper(CellLayout layout) {
45         super(layout);
46     }
47 
48     /**
49      * Find the virtual view id corresponding to the top left corner of any drop region by which
50      * the passed id is contained. For an icon, this is simply
51      */
52     @Override
intersectsValidDropTarget(int id)53     protected int intersectsValidDropTarget(int id) {
54         int mCountX = mView.getCountX();
55         int mCountY = mView.getCountY();
56 
57         int x = id % mCountX;
58         int y = id / mCountX;
59         LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
60 
61         if (dragInfo.dragType == DragType.WIDGET && !mView.acceptsWidget()) {
62             return INVALID_POSITION;
63         }
64 
65         if (dragInfo.dragType == DragType.WIDGET) {
66             // For a widget, every cell must be vacant. In addition, we will return any valid
67             // drop target by which the passed id is contained.
68             boolean fits = false;
69 
70             // These represent the amount that we can back off if we hit a problem. They
71             // get consumed as we move up and to the right, trying new regions.
72             int spanX = dragInfo.info.spanX;
73             int spanY = dragInfo.info.spanY;
74 
75             for (int m = 0; m < spanX; m++) {
76                 for (int n = 0; n < spanY; n++) {
77 
78                     fits = true;
79                     int x0 = x - m;
80                     int y0 = y - n;
81 
82                     if (x0 < 0 || y0 < 0) continue;
83 
84                     for (int i = x0; i < x0 + spanX; i++) {
85                         if (!fits) break;
86                         for (int j = y0; j < y0 + spanY; j++) {
87                             if (i >= mCountX || j >= mCountY || mView.isOccupied(i, j)) {
88                                 fits = false;
89                                 break;
90                             }
91                         }
92                     }
93                     if (fits) {
94                         return x0 + mCountX * y0;
95                     }
96                 }
97             }
98             return INVALID_POSITION;
99         } else {
100             // For an icon, we simply check the view directly below
101             View child = mView.getChildAt(x, y);
102             if (child == null || child == dragInfo.item) {
103                 // Empty cell. Good for an icon or folder.
104                 return id;
105             } else if (dragInfo.dragType != DragType.FOLDER) {
106                 // For icons, we can consider cells that have another icon or a folder.
107                 ItemInfo info = (ItemInfo) child.getTag();
108                 if (info instanceof AppInfo || info instanceof FolderInfo ||
109                         info instanceof WorkspaceItemInfo) {
110                     return id;
111                 }
112             }
113             return INVALID_POSITION;
114         }
115     }
116 
117     @Override
getConfirmationForIconDrop(int id)118     protected String getConfirmationForIconDrop(int id) {
119         int x = id % mView.getCountX();
120         int y = id / mView.getCountX();
121         LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
122 
123         View child = mView.getChildAt(x, y);
124         if (child == null || child == dragInfo.item) {
125             return mContext.getString(R.string.item_moved);
126         } else {
127             ItemInfo info = (ItemInfo) child.getTag();
128             if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) {
129                 return mContext.getString(R.string.folder_created);
130 
131             } else if (info instanceof FolderInfo) {
132                 return mContext.getString(R.string.added_to_folder);
133             }
134         }
135         return "";
136     }
137 
138     @Override
onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node)139     protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) {
140         super.onPopulateNodeForVirtualView(id, node);
141 
142 
143         // ExploreByTouchHelper does not currently handle view scale.
144         // Update BoundsInScreen to appropriate value.
145         DragLayer dragLayer = Launcher.getLauncher(mView.getContext()).getDragLayer();
146         mTempCords[0] = mTempCords[1] = 0;
147         float scale = dragLayer.getDescendantCoordRelativeToSelf(mView, mTempCords);
148 
149         node.getBoundsInParent(mTempRect);
150         mTempRect.left = mTempCords[0] + (int) (mTempRect.left * scale);
151         mTempRect.right = mTempCords[0] + (int) (mTempRect.right * scale);
152         mTempRect.top = mTempCords[1] + (int) (mTempRect.top * scale);
153         mTempRect.bottom = mTempCords[1] + (int) (mTempRect.bottom * scale);
154         node.setBoundsInScreen(mTempRect);
155     }
156 
157     @Override
getLocationDescriptionForIconDrop(int id)158     protected String getLocationDescriptionForIconDrop(int id) {
159         int x = id % mView.getCountX();
160         int y = id / mView.getCountX();
161         LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo();
162 
163         View child = mView.getChildAt(x, y);
164         if (child == null || child == dragInfo.item) {
165             return mView.getItemMoveDescription(x, y);
166         } else {
167             return getDescriptionForDropOver(child, mContext);
168         }
169     }
170 
getDescriptionForDropOver(View overChild, Context context)171     public static String getDescriptionForDropOver(View overChild, Context context) {
172         ItemInfo info = (ItemInfo) overChild.getTag();
173         if (info instanceof WorkspaceItemInfo) {
174             return context.getString(R.string.create_folder_with, info.title);
175         } else if (info instanceof FolderInfo) {
176             if (TextUtils.isEmpty(info.title)) {
177                 // Find the first item in the folder.
178                 FolderInfo folder = (FolderInfo) info;
179                 WorkspaceItemInfo firstItem = null;
180                 for (WorkspaceItemInfo shortcut : folder.contents) {
181                     if (firstItem == null || firstItem.rank > shortcut.rank) {
182                         firstItem = shortcut;
183                     }
184                 }
185 
186                 if (firstItem != null) {
187                     return context.getString(R.string.add_to_folder_with_app, firstItem.title);
188                 }
189             }
190             return context.getString(R.string.add_to_folder, info.title);
191         }
192         return "";
193     }
194 }
195