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