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