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