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