1 /* 2 * Copyright (C) 2016 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.settings.localepicker; 18 19 import android.content.Context; 20 import android.os.Bundle; 21 import android.view.View; 22 23 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; 24 import androidx.recyclerview.widget.LinearLayoutManager; 25 import androidx.recyclerview.widget.RecyclerView; 26 27 import com.android.settings.R; 28 29 /** 30 * Add accessibility actions to the drag-and-drop locale list 31 * 32 * <p>Dragging is not supported neither by TalkBack or the accessibility 33 * framework at the moment. So we need custom actions to be able 34 * to change the order of the locales.</p> 35 * 36 * <p>Also, the remove functionality is difficult to discover and use 37 * with TalkBack only, so we are also adding a "remove" action.</p> 38 * 39 * <p>It only removes one locale at the time, but most users don't 40 * really add many locales "by mistake", so there is no real need 41 * to delete a lot of locales at once.</p> 42 */ 43 public class LocaleLinearLayoutManager extends LinearLayoutManager { 44 private final LocaleDragAndDropAdapter mAdapter; 45 private final Context mContext; 46 private LocaleListEditor mLocaleListEditor; 47 48 private final AccessibilityNodeInfoCompat.AccessibilityActionCompat mActionMoveUp; 49 private final AccessibilityNodeInfoCompat.AccessibilityActionCompat mActionMoveDown; 50 private final AccessibilityNodeInfoCompat.AccessibilityActionCompat mActionMoveTop; 51 private final AccessibilityNodeInfoCompat.AccessibilityActionCompat mActionMoveBottom; 52 private final AccessibilityNodeInfoCompat.AccessibilityActionCompat mActionRemove; 53 LocaleLinearLayoutManager(Context context, LocaleDragAndDropAdapter adapter)54 public LocaleLinearLayoutManager(Context context, LocaleDragAndDropAdapter adapter) { 55 super(context); 56 this.mContext = context; 57 this.mAdapter = adapter; 58 59 this.mActionMoveUp = new AccessibilityNodeInfoCompat.AccessibilityActionCompat( 60 R.id.action_drag_move_up, 61 mContext.getString(R.string.action_drag_label_move_up)); 62 this.mActionMoveDown = new AccessibilityNodeInfoCompat.AccessibilityActionCompat( 63 R.id.action_drag_move_down, 64 mContext.getString(R.string.action_drag_label_move_down)); 65 this.mActionMoveTop = new AccessibilityNodeInfoCompat.AccessibilityActionCompat( 66 R.id.action_drag_move_top, 67 mContext.getString(R.string.action_drag_label_move_top)); 68 this.mActionMoveBottom = new AccessibilityNodeInfoCompat.AccessibilityActionCompat( 69 R.id.action_drag_move_bottom, 70 mContext.getString(R.string.action_drag_label_move_bottom)); 71 this.mActionRemove = new AccessibilityNodeInfoCompat.AccessibilityActionCompat( 72 R.id.action_drag_remove, 73 mContext.getString(R.string.action_drag_label_remove)); 74 } 75 76 @Override onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, RecyclerView.State state, View host, AccessibilityNodeInfoCompat info)77 public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, 78 RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) { 79 80 super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info); 81 82 final int itemCount = this.getItemCount(); 83 final int position = this.getPosition(host); 84 final LocaleDragCell dragCell = (LocaleDragCell) host; 85 86 // We want the description to be something not localizable, so that any TTS engine for 87 // any language can handle it. And we want the position to be part of it. 88 // So we use something like "2, French (France)" 89 final String description = 90 (position + 1) + ", " + dragCell.getLabelView().getContentDescription(); 91 info.setContentDescription(description); 92 93 if (mAdapter.isRemoveMode()) { // We don't move things around in remove mode 94 return; 95 } 96 97 // The order in which we add the actions is important for the circular selection menu. 98 // With the current order the "up" action will be (more or less) up, and "down" more 99 // or less down ("more or less" because we have 5 actions) 100 if (position > 0) { // it is not the first one 101 info.addAction(mActionMoveUp); 102 info.addAction(mActionMoveTop); 103 } 104 if (position + 1 < itemCount) { // it is not the last one 105 info.addAction(mActionMoveDown); 106 info.addAction(mActionMoveBottom); 107 } 108 if (itemCount > 1) { 109 info.addAction(mActionRemove); 110 } 111 } 112 113 @Override performAccessibilityActionForItem(RecyclerView.Recycler recycler, RecyclerView.State state, View host, int action, Bundle args)114 public boolean performAccessibilityActionForItem(RecyclerView.Recycler recycler, 115 RecyclerView.State state, View host, int action, Bundle args) { 116 117 final int itemCount = this.getItemCount(); 118 final int position = this.getPosition(host); 119 boolean result = false; 120 121 if (action == R.id.action_drag_move_up) { 122 if (position > 0) { 123 mAdapter.onItemMove(position, position - 1); 124 result = true; 125 } 126 } else if (action == R.id.action_drag_move_down) { 127 if (position + 1 < itemCount) { 128 mAdapter.onItemMove(position, position + 1); 129 result = true; 130 } 131 } else if (action == R.id.action_drag_move_top) { 132 if (position != 0) { 133 mAdapter.onItemMove(position, 0); 134 result = true; 135 } 136 } else if (action == R.id.action_drag_move_bottom) { 137 if (position != itemCount - 1) { 138 mAdapter.onItemMove(position, itemCount - 1); 139 result = true; 140 } 141 } else if (action == R.id.action_drag_remove) { 142 if (itemCount > 1) { 143 mAdapter.removeItem(position); 144 result = true; 145 } 146 } else { 147 return super.performAccessibilityActionForItem(recycler, state, host, action, args); 148 } 149 150 if (result) { 151 mLocaleListEditor.showConfirmDialog(false, mAdapter.getFeedItemList().get(0)); 152 } 153 return result; 154 } 155 setLocaleListEditor(LocaleListEditor localeListEditor)156 public void setLocaleListEditor(LocaleListEditor localeListEditor) { 157 mLocaleListEditor = localeListEditor; 158 } 159 } 160