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 }