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