1 /* 2 * Copyright (C) 2020 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.systemui.qs.customize; 18 19 import android.os.Bundle; 20 import android.view.View; 21 import android.view.accessibility.AccessibilityNodeInfo; 22 23 import androidx.core.view.AccessibilityDelegateCompat; 24 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 25 26 import com.android.systemui.res.R; 27 28 import java.util.List; 29 30 /** 31 * Accessibility delegate for {@link TileAdapter} views. 32 * 33 * This delegate will populate the accessibility info with the proper actions that can be taken for 34 * the different tiles: 35 * <ul> 36 * <li>Add to end if the tile is not a current tile (by double tap).</li> 37 * <li>Add to a given position (by context menu). This will let the user select a position.</li> 38 * <li>Remove, if the tile is a current tile (by double tap).</li> 39 * <li>Move to a given position (by context menu). This will let the user select a position.</li> 40 * </ul> 41 * 42 * This only handles generating the associated actions. The logic for selecting positions is handled 43 * by {@link TileAdapter}. 44 * 45 * In order for the delegate to work properly, the asociated {@link TileAdapter.Holder} should be 46 * passed along with the view using {@link View#setTag}. 47 */ 48 class TileAdapterDelegate extends AccessibilityDelegateCompat { 49 50 private static final int MOVE_TO_POSITION_ID = R.id.accessibility_action_qs_move_to_position; 51 private static final int ADD_TO_POSITION_ID = R.id.accessibility_action_qs_add_to_position; 52 getHolder(View view)53 private TileAdapter.Holder getHolder(View view) { 54 return (TileAdapter.Holder) view.getTag(); 55 } 56 57 @Override onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info)58 public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { 59 super.onInitializeAccessibilityNodeInfo(host, info); 60 TileAdapter.Holder holder = getHolder(host); 61 info.setCollectionItemInfo(null); 62 info.setStateDescription(""); 63 if (holder == null || !holder.canTakeAccessibleAction()) { 64 // If there's not a holder (not a regular Tile) or an action cannot be taken 65 // because we are in the middle of an accessibility action, don't create a special node. 66 return; 67 } 68 69 addClickAction(host, info, holder); 70 maybeAddActionAddToPosition(host, info, holder); 71 maybeAddActionMoveToPosition(host, info, holder); 72 73 if (holder.isCurrentTile()) { 74 info.setStateDescription(host.getContext().getString( 75 R.string.accessibility_qs_edit_position, holder.getLayoutPosition())); 76 } 77 } 78 79 @Override performAccessibilityAction(View host, int action, Bundle args)80 public boolean performAccessibilityAction(View host, int action, Bundle args) { 81 TileAdapter.Holder holder = getHolder(host); 82 83 if (holder == null || !holder.canTakeAccessibleAction()) { 84 // If there's not a holder (not a regular Tile) or an action cannot be taken 85 // because we are in the middle of an accessibility action, perform the default action. 86 return super.performAccessibilityAction(host, action, args); 87 } 88 if (action == AccessibilityNodeInfo.ACTION_CLICK) { 89 holder.toggleState(); 90 return true; 91 } else if (action == MOVE_TO_POSITION_ID) { 92 holder.startAccessibleMove(); 93 return true; 94 } else if (action == ADD_TO_POSITION_ID) { 95 holder.startAccessibleAdd(); 96 return true; 97 } else { 98 return super.performAccessibilityAction(host, action, args); 99 } 100 } 101 addClickAction( View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder)102 private void addClickAction( 103 View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder) { 104 String clickActionString; 105 if (holder.canAdd()) { 106 clickActionString = host.getContext().getString( 107 R.string.accessibility_qs_edit_tile_add_action); 108 } else if (holder.canRemove()) { 109 clickActionString = host.getContext().getString( 110 R.string.accessibility_qs_edit_remove_tile_action); 111 } else { 112 // Remove the default click action if tile can't either be added or removed (for example 113 // if there's the minimum number of tiles) 114 List<AccessibilityNodeInfoCompat.AccessibilityActionCompat> listOfActions = 115 info.getActionList(); // This is a copy 116 int numActions = listOfActions.size(); 117 for (int i = 0; i < numActions; i++) { 118 if (listOfActions.get(i).getId() == AccessibilityNodeInfo.ACTION_CLICK) { 119 info.removeAction(listOfActions.get(i)); 120 } 121 } 122 // We really don't want it to be clickable in this case. 123 info.setClickable(false); 124 return; 125 } 126 127 AccessibilityNodeInfoCompat.AccessibilityActionCompat action = 128 new AccessibilityNodeInfoCompat.AccessibilityActionCompat( 129 AccessibilityNodeInfo.ACTION_CLICK, clickActionString); 130 info.addAction(action); 131 info.setClickable(true); 132 } 133 maybeAddActionMoveToPosition( View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder)134 private void maybeAddActionMoveToPosition( 135 View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder) { 136 if (holder.isCurrentTile()) { 137 AccessibilityNodeInfoCompat.AccessibilityActionCompat action = 138 new AccessibilityNodeInfoCompat.AccessibilityActionCompat(MOVE_TO_POSITION_ID, 139 host.getContext().getString( 140 R.string.accessibility_qs_edit_tile_start_move)); 141 info.addAction(action); 142 } 143 } 144 maybeAddActionAddToPosition( View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder)145 private void maybeAddActionAddToPosition( 146 View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder) { 147 if (holder.canAdd()) { 148 AccessibilityNodeInfoCompat.AccessibilityActionCompat action = 149 new AccessibilityNodeInfoCompat.AccessibilityActionCompat(ADD_TO_POSITION_ID, 150 host.getContext().getString( 151 R.string.accessibility_qs_edit_tile_start_add)); 152 info.addAction(action); 153 } 154 } 155 } 156