diff --git a/res/layout/locale_drag_cell.xml b/res/layout/locale_drag_cell.xml index 522f28428e9..db9454a21cd 100644 --- a/res/layout/locale_drag_cell.xml +++ b/res/layout/locale_drag_cell.xml @@ -68,6 +68,7 @@ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:layout_gravity="center_vertical" android:src="@drawable/drag_handle" + android:importantForAccessibility="no" android:layout_alignParentEnd="true" android:layout_alignTop="@id/checkbox" android:layout_alignBottom="@id/checkbox"/> diff --git a/res/values/ids.xml b/res/values/ids.xml index f9334e43af5..dcf279aac0e 100644 --- a/res/values/ids.xml +++ b/res/values/ids.xml @@ -24,4 +24,11 @@ + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index eb2d30acbc6..535c48a0d57 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -530,6 +530,17 @@ May not be available in some apps + + Move up + + Move down + + Move to top + + Move to bottom + + Remove language + Choose activity diff --git a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java index 2d22b06a9cf..bc17814ba50 100644 --- a/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java +++ b/src/com/android/settings/localepicker/LocaleDragAndDropAdapter.java @@ -38,7 +38,6 @@ import com.android.settings.R; import java.text.NumberFormat; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Locale; @@ -184,12 +183,14 @@ class LocaleDragAndDropAdapter return itemCount; } - private void onItemMove(int fromPosition, int toPosition) { + void onItemMove(int fromPosition, int toPosition) { if (fromPosition >= 0 && toPosition >= 0) { - Collections.swap(mFeedItemList, fromPosition, toPosition); + final LocaleStore.LocaleInfo saved = mFeedItemList.get(fromPosition); + mFeedItemList.remove(fromPosition); + mFeedItemList.add(toPosition, saved); } else { // TODO: It looks like sometimes the RecycleView tries to swap item -1 - // Investigate and file a bug. + // I did not see it in a while, but if it happens, investigate and file a bug. Log.e(TAG, String.format(Locale.US, "Negative position in onItemMove %d -> %d", fromPosition, toPosition)); } @@ -207,6 +208,23 @@ class LocaleDragAndDropAdapter } } + boolean isRemoveMode() { + return mRemoveMode; + } + + void removeItem(int position) { + int itemCount = mFeedItemList.size(); + if (itemCount <= 1) { + return; + } + if (position < 0 || position >= itemCount) { + return; + } + mFeedItemList.remove(position); + notifyDataSetChanged(); + doTheUpdate(); + } + void removeChecked() { int itemCount = mFeedItemList.size(); for (int i = itemCount - 1; i >= 0; i--) { diff --git a/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java b/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java new file mode 100644 index 00000000000..630f3825664 --- /dev/null +++ b/src/com/android/settings/localepicker/LocaleLinearLayoutManager.java @@ -0,0 +1,153 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settings.localepicker; + +import android.content.Context; +import android.os.Bundle; +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.view.View; + +import com.android.settings.R; + +/** + * Add accessibility actions to the drag-and-drop locale list + * + *

Dragging is not supported neither by TalkBack or the accessibility + * framework at the moment. So we need custom actions to be able + * to change the order of the locales.

+ * + *

Also, the remove functionality is difficult to discover and use + * with TalkBack only, so we are also adding a "remove" action.

+ * + *

It only removes one locale at the time, but most users don't + * really add many locales "by mistake", so there is no real need + * to delete a lot of locales at once.

+ */ +public class LocaleLinearLayoutManager extends LinearLayoutManager { + private final LocaleDragAndDropAdapter mAdapter; + private final Context mContext; + + private final AccessibilityNodeInfoCompat.AccessibilityActionCompat mActionMoveUp; + private final AccessibilityNodeInfoCompat.AccessibilityActionCompat mActionMoveDown; + private final AccessibilityNodeInfoCompat.AccessibilityActionCompat mActionMoveTop; + private final AccessibilityNodeInfoCompat.AccessibilityActionCompat mActionMoveBottom; + private final AccessibilityNodeInfoCompat.AccessibilityActionCompat mActionRemove; + + public LocaleLinearLayoutManager(Context context, LocaleDragAndDropAdapter adapter) { + super(context); + this.mContext = context; + this.mAdapter = adapter; + + this.mActionMoveUp = new AccessibilityNodeInfoCompat.AccessibilityActionCompat( + R.id.action_drag_move_up, + mContext.getString(R.string.action_drag_label_move_up)); + this.mActionMoveDown = new AccessibilityNodeInfoCompat.AccessibilityActionCompat( + R.id.action_drag_move_down, + mContext.getString(R.string.action_drag_label_move_down)); + this.mActionMoveTop = new AccessibilityNodeInfoCompat.AccessibilityActionCompat( + R.id.action_drag_move_top, + mContext.getString(R.string.action_drag_label_move_top)); + this.mActionMoveBottom = new AccessibilityNodeInfoCompat.AccessibilityActionCompat( + R.id.action_drag_move_bottom, + mContext.getString(R.string.action_drag_label_move_bottom)); + this.mActionRemove = new AccessibilityNodeInfoCompat.AccessibilityActionCompat( + R.id.action_drag_remove, + mContext.getString(R.string.action_drag_label_remove)); + } + + @Override + public void onInitializeAccessibilityNodeInfoForItem(RecyclerView.Recycler recycler, + RecyclerView.State state, View host, AccessibilityNodeInfoCompat info) { + + super.onInitializeAccessibilityNodeInfoForItem(recycler, state, host, info); + + final int itemCount = this.getItemCount(); + final int position = this.getPosition(host); + final LocaleDragCell dragCell = (LocaleDragCell) host; + + // We want the description to be something not localizable, so that any TTS engine for + // any language can handle it. And we want the position to be part of it. + // So we use something like "2, French (France)" + final String description = + (position + 1) + ", " + dragCell.getCheckbox().getContentDescription(); + info.setContentDescription(description); + + if (mAdapter.isRemoveMode()) { // We don't move things around in remove mode + return; + } + + // The order in which we add the actions is important for the circular selection menu. + // With the current order the "up" action will be (more or less) up, and "down" more + // or less down ("more or less" because we have 5 actions) + if (position > 0) { // it is not the first one + info.addAction(mActionMoveUp); + info.addAction(mActionMoveTop); + } + if (position + 1 < itemCount) { // it is not the last one + info.addAction(mActionMoveDown); + info.addAction(mActionMoveBottom); + } + if (itemCount > 1) { + info.addAction(mActionRemove); + } + } + + @Override + public boolean performAccessibilityActionForItem(RecyclerView.Recycler recycler, + RecyclerView.State state, View host, int action, Bundle args) { + + final int itemCount = this.getItemCount(); + final int position = this.getPosition(host); + + switch (action) { + case R.id.action_drag_move_up: + if (position > 0) { + mAdapter.onItemMove(position, position - 1); + return true; + } + return false; + case R.id.action_drag_move_down: + if (position + 1 < itemCount) { + mAdapter.onItemMove(position, position + 1); + return true; + } + return false; + case R.id.action_drag_move_top: + if (position != 0) { + mAdapter.onItemMove(position, 0); + return true; + } + return false; + case R.id.action_drag_move_bottom: + if (position != itemCount - 1) { + mAdapter.onItemMove(position, itemCount - 1); + return true; + } + return false; + case R.id.action_drag_remove: + if (itemCount > 1) { + mAdapter.removeItem(position); + return true; + } + return false; + default: + return super.performAccessibilityActionForItem(recycler, state, host, action, args); + } + } +} diff --git a/src/com/android/settings/localepicker/LocaleListEditor.java b/src/com/android/settings/localepicker/LocaleListEditor.java index 3287d393064..b95a5907be9 100644 --- a/src/com/android/settings/localepicker/LocaleListEditor.java +++ b/src/com/android/settings/localepicker/LocaleListEditor.java @@ -21,7 +21,6 @@ import android.app.FragmentTransaction; import android.content.Context; import android.content.DialogInterface; import android.os.Bundle; -import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.LocaleList; import android.view.LayoutInflater; @@ -30,7 +29,6 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.LinearLayout; import com.android.internal.app.LocalePicker; import com.android.internal.app.LocalePickerWithRegion; @@ -76,9 +74,8 @@ public class LocaleListEditor extends SettingsPreferenceFragment @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstState) { - View result = super.onCreateView(inflater, container, savedInstState); - LinearLayout ll = (LinearLayout) result; - View myLayout = inflater.inflate(R.layout.locale_order_list, ll); + final View result = super.onCreateView(inflater, container, savedInstState); + final View myLayout = inflater.inflate(R.layout.locale_order_list, (ViewGroup) result); getActivity().setTitle(R.string.pref_title_lang_selection); @@ -236,7 +233,7 @@ public class LocaleListEditor extends SettingsPreferenceFragment private void configureDragAndDrop(View view) { final RecyclerView list = (RecyclerView) view.findViewById(R.id.dragList); - final LinearLayoutManager llm = new LinearLayoutManager(this.getContext()); + final LocaleLinearLayoutManager llm = new LocaleLinearLayoutManager(getContext(), mAdapter); llm.setAutoMeasureEnabled(true); list.setLayoutManager(llm);