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.os.Bundle; 22 import android.view.View; 23 import android.view.View.OnClickListener; 24 import android.view.accessibility.AccessibilityEvent; 25 26 import com.android.launcher3.CellLayout; 27 import com.android.launcher3.Launcher; 28 import com.android.launcher3.R; 29 30 import java.util.List; 31 32 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 33 import androidx.customview.widget.ExploreByTouchHelper; 34 35 /** 36 * Helper class to make drag-and-drop in a {@link CellLayout} accessible. 37 */ 38 public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHelper 39 implements OnClickListener { 40 protected static final int INVALID_POSITION = -1; 41 42 private static final int[] sTempArray = new int[2]; 43 44 protected final CellLayout mView; 45 protected final Context mContext; 46 protected final LauncherAccessibilityDelegate mDelegate; 47 48 private final Rect mTempRect = new Rect(); 49 DragAndDropAccessibilityDelegate(CellLayout forView)50 public DragAndDropAccessibilityDelegate(CellLayout forView) { 51 super(forView); 52 mView = forView; 53 mContext = mView.getContext(); 54 mDelegate = Launcher.getLauncher(mContext).getAccessibilityDelegate(); 55 } 56 57 @Override getVirtualViewAt(float x, float y)58 protected int getVirtualViewAt(float x, float y) { 59 if (x < 0 || y < 0 || x > mView.getMeasuredWidth() || y > mView.getMeasuredHeight()) { 60 return INVALID_ID; 61 } 62 mView.pointToCellExact((int) x, (int) y, sTempArray); 63 64 // Map cell to id 65 int id = sTempArray[0] + sTempArray[1] * mView.getCountX(); 66 return intersectsValidDropTarget(id); 67 } 68 69 /** 70 * @return the view id of the top left corner of a valid drop region or 71 * {@link #INVALID_POSITION} if there is no such valid region. 72 */ intersectsValidDropTarget(int id)73 protected abstract int intersectsValidDropTarget(int id); 74 75 @Override getVisibleVirtualViews(List<Integer> virtualViews)76 protected void getVisibleVirtualViews(List<Integer> virtualViews) { 77 // We create a virtual view for each cell of the grid 78 // The cell ids correspond to cells in reading order. 79 int nCells = mView.getCountX() * mView.getCountY(); 80 81 for (int i = 0; i < nCells; i++) { 82 if (intersectsValidDropTarget(i) == i) { 83 virtualViews.add(i); 84 } 85 } 86 } 87 88 @Override onPerformActionForVirtualView(int viewId, int action, Bundle args)89 protected boolean onPerformActionForVirtualView(int viewId, int action, Bundle args) { 90 if (action == AccessibilityNodeInfoCompat.ACTION_CLICK && viewId != INVALID_ID) { 91 String confirmation = getConfirmationForIconDrop(viewId); 92 mDelegate.handleAccessibleDrop(mView, getItemBounds(viewId), confirmation); 93 return true; 94 } 95 return false; 96 } 97 98 @Override onClick(View v)99 public void onClick(View v) { 100 onPerformActionForVirtualView(getFocusedVirtualView(), 101 AccessibilityNodeInfoCompat.ACTION_CLICK, null); 102 } 103 104 @Override onPopulateEventForVirtualView(int id, AccessibilityEvent event)105 protected void onPopulateEventForVirtualView(int id, AccessibilityEvent event) { 106 if (id == INVALID_ID) { 107 throw new IllegalArgumentException("Invalid virtual view id"); 108 } 109 event.setContentDescription(mContext.getString(R.string.action_move_here)); 110 } 111 112 @Override onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node)113 protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) { 114 if (id == INVALID_ID) { 115 throw new IllegalArgumentException("Invalid virtual view id"); 116 } 117 118 node.setContentDescription(getLocationDescriptionForIconDrop(id)); 119 node.setBoundsInParent(getItemBounds(id)); 120 121 node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); 122 node.setClickable(true); 123 node.setFocusable(true); 124 } 125 getLocationDescriptionForIconDrop(int id)126 protected abstract String getLocationDescriptionForIconDrop(int id); 127 getConfirmationForIconDrop(int id)128 protected abstract String getConfirmationForIconDrop(int id); 129 getItemBounds(int id)130 private Rect getItemBounds(int id) { 131 int cellX = id % mView.getCountX(); 132 int cellY = id / mView.getCountX(); 133 LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo(); 134 mView.cellToRect(cellX, cellY, dragInfo.info.spanX, dragInfo.info.spanY, mTempRect); 135 return mTempRect; 136 } 137 }