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