/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.accessibility; import android.content.Context; import android.graphics.Rect; import android.os.Bundle; import android.view.MotionEvent; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnHoverListener; import android.view.accessibility.AccessibilityEvent; import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; import androidx.customview.widget.ExploreByTouchHelper; import com.android.launcher3.CellLayout; import com.android.launcher3.Launcher; import com.android.launcher3.R; import java.util.List; /** * Helper class to make drag-and-drop in a {@link CellLayout} accessible. */ public abstract class DragAndDropAccessibilityDelegate extends ExploreByTouchHelper implements OnClickListener, OnHoverListener { protected static final int INVALID_POSITION = -1; private static final int[] sTempArray = new int[2]; protected final CellLayout mView; protected final Context mContext; protected final LauncherAccessibilityDelegate mDelegate; private final Rect mTempRect = new Rect(); public DragAndDropAccessibilityDelegate(CellLayout forView) { super(forView); mView = forView; mContext = mView.getContext(); mDelegate = Launcher.getLauncher(mContext).getAccessibilityDelegate(); } @Override protected int getVirtualViewAt(float x, float y) { if (x < 0 || y < 0 || x > mView.getMeasuredWidth() || y > mView.getMeasuredHeight()) { return INVALID_ID; } mView.pointToCellExact((int) x, (int) y, sTempArray); // Map cell to id int id = sTempArray[0] + sTempArray[1] * mView.getCountX(); return intersectsValidDropTarget(id); } /** * @return the view id of the top left corner of a valid drop region or * {@link #INVALID_POSITION} if there is no such valid region. */ protected abstract int intersectsValidDropTarget(int id); @Override protected void getVisibleVirtualViews(List virtualViews) { // We create a virtual view for each cell of the grid // The cell ids correspond to cells in reading order. int nCells = mView.getCountX() * mView.getCountY(); for (int i = 0; i < nCells; i++) { if (intersectsValidDropTarget(i) == i) { virtualViews.add(i); } } } @Override protected boolean onPerformActionForVirtualView(int viewId, int action, Bundle args) { if (action == AccessibilityNodeInfoCompat.ACTION_CLICK && viewId != INVALID_ID) { String confirmation = getConfirmationForIconDrop(viewId); mDelegate.handleAccessibleDrop(mView, getItemBounds(viewId), confirmation); return true; } return false; } @Override public void onClick(View v) { onPerformActionForVirtualView(getFocusedVirtualView(), AccessibilityNodeInfoCompat.ACTION_CLICK, null); } @Override protected void onPopulateEventForVirtualView(int id, AccessibilityEvent event) { if (id == INVALID_ID) { throw new IllegalArgumentException("Invalid virtual view id"); } event.setContentDescription(mContext.getString(R.string.action_move_here)); } @Override protected void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) { if (id == INVALID_ID) { throw new IllegalArgumentException("Invalid virtual view id"); } node.setContentDescription(getLocationDescriptionForIconDrop(id)); node.setBoundsInParent(getItemBounds(id)); node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); node.setClickable(true); node.setFocusable(true); } @Override public boolean onHover(View view, MotionEvent motionEvent) { return dispatchHoverEvent(motionEvent); } protected abstract String getLocationDescriptionForIconDrop(int id); protected abstract String getConfirmationForIconDrop(int id); private Rect getItemBounds(int id) { int cellX = id % mView.getCountX(); int cellY = id / mView.getCountX(); LauncherAccessibilityDelegate.DragInfo dragInfo = mDelegate.getDragInfo(); mView.cellToRect(cellX, cellY, dragInfo.info.spanX, dragInfo.info.spanY, mTempRect); return mTempRect; } }