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
17package com.android.settings.localepicker;
18
19import android.content.Context;
20import android.os.Bundle;
21import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
22import android.support.v7.widget.LinearLayoutManager;
23import android.support.v7.widget.RecyclerView;
24import android.view.View;
25
26import 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 */
42public 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
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
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
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