/* * 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 com.android.launcher3.dragndrop.DragLayer; 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; protected final Rect mTempRect = new Rect(); protected final int[] mTempCords = new int[2]; protected final CellLayout mView; protected final Context mContext; protected final LauncherAccessibilityDelegate mDelegate; protected final DragLayer mDragLayer; public DragAndDropAccessibilityDelegate(CellLayout forView) { super(forView); mView = forView; mContext = mView.getContext(); Launcher launcher = Launcher.getLauncher(mContext); mDelegate = launcher.getAccessibilityDelegate(); mDragLayer = launcher.getDragLayer(); } @Override public 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, mTempCords); // Map cell to id int id = mTempCords[0] + mTempCords[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 public void getVisibleVirtualViews(List<Integer> 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 public 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 public void onPopulateNodeForVirtualView(int id, AccessibilityNodeInfoCompat node) { if (id == INVALID_ID) { throw new IllegalArgumentException("Invalid virtual view id"); } node.setContentDescription(getLocationDescriptionForIconDrop(id)); Rect itemBounds = getItemBounds(id); node.setBoundsInParent(itemBounds); // ExploreByTouchHelper does not currently handle view scale. // Update BoundsInScreen to appropriate value. mTempCords[0] = mTempCords[1] = 0; float scale = mDragLayer.getDescendantCoordRelativeToSelf(mView, mTempCords); mTempRect.left = mTempCords[0] + (int) (itemBounds.left * scale); mTempRect.right = mTempCords[0] + (int) (itemBounds.right * scale); mTempRect.top = mTempCords[1] + (int) (itemBounds.top * scale); mTempRect.bottom = mTempCords[1] + (int) (itemBounds.bottom * scale); node.setBoundsInScreen(mTempRect); node.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK); node.setClickable(true); node.setFocusable(true); } @Override public boolean onHover(View view, MotionEvent motionEvent) { return dispatchHoverEvent(motionEvent); } /** * Returns the target host container */ public View getHost() { return mView; } 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; } }