diff options
| author | 2020-09-25 14:44:39 +0000 | |
|---|---|---|
| committer | 2020-09-25 14:44:39 +0000 | |
| commit | e0123b0d238908a3d2531d46f02809cce5b689ea (patch) | |
| tree | b2ce57233e9cc0293daa82890b5f5622bf626980 | |
| parent | 6842f03c9d2f128785df5ce2bd02c61f35226554 (diff) | |
| parent | 7a5e333ba5e8fc5e71d33526f478167c4374da70 (diff) | |
Merge "Add TileAdapterDelegate" into rvc-qpr-dev
5 files changed, 568 insertions, 93 deletions
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index a56f6f56836a..b8e8db5ecf50 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -177,6 +177,9 @@ <item type="id" name="accessibility_action_controls_move_before" /> <item type="id" name="accessibility_action_controls_move_after" /> + <item type="id" name="accessibility_action_qs_move_to_position" /> + <item type="id" name="accessibility_action_qs_add_to_position" /> + <!-- Accessibility actions for PIP --> <item type="id" name="action_pip_resize" /> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 38501eb8da3d..77ce39fe9953 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2267,23 +2267,26 @@ <!-- Accessibility action for moving docked stack divider to make the bottom screen full screen [CHAR LIMIT=NONE] --> <string name="accessibility_action_divider_bottom_full">Bottom full screen</string> - <!-- Accessibility description of a QS tile while editing positions [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_tile_label">Position <xliff:g id="position" example="2">%1$d</xliff:g>, <xliff:g id="tile_name" example="Wi-Fi">%2$s</xliff:g>. Double tap to edit.</string> + <!-- Accessibility description of action to remove QS tile on click. It will read as "Double-tap to remove tile" in screen readers [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_remove_tile_action">remove tile</string> - <!-- Accessibility description of a QS tile while editing positions [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_add_tile_label"><xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g>. Double tap to add.</string> + <!-- Accessibility action of action to add QS tile to end. It will read as "Double-tap to add tile to end" in screen readers [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_add_action">add tile to end</string> - <!-- Accessibility description of option to move QS tile [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_move_tile">Move <xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g></string> + <!-- Accessibility action for context menu to move QS tile [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_start_move">Move tile</string> - <!-- Accessibility description of option to remove QS tile [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_remove_tile">Remove <xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g></string> + <!-- Accessibility action for context menu to add QS tile [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_start_add">Add tile</string> - <!-- Accessibility action when QS tile is to be added [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_tile_add">Add <xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g> to position <xliff:g id="position" example="5">%2$d</xliff:g></string> + <!-- Accessibility description when QS tile is to be moved, indicating the destination position [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_move_to_position">Move to <xliff:g id="position" example="5">%1$d</xliff:g></string> - <!-- Accessibility action when QS tile is to be moved [CHAR LIMIT=NONE] --> - <string name="accessibility_qs_edit_tile_move">Move <xliff:g id="tile_name" example="Wi-Fi">%1$s</xliff:g> to position <xliff:g id="position" example="5">%2$d</xliff:g></string> + <!-- Accessibility description when QS tile is to be added, indicating the destination position [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_tile_add_to_position">Add to position <xliff:g id="position" example="5">%1$d</xliff:g></string> + + <!-- Accessibility description indicating the currently selected tile's position. Only used for tiles that are currently in use [CHAR LIMIT=NONE] --> + <string name="accessibility_qs_edit_position">Position <xliff:g id="position" example="5">%1$d</xliff:g></string> <!-- Accessibility label for window when QS editing is happening [CHAR LIMIT=NONE] --> <string name="accessibility_desc_quick_settings_edit">Quick settings editor.</string> diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java index e738cec4962a..bffeb3ec3c70 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java @@ -14,11 +14,8 @@ package com.android.systemui.qs.customize; -import android.app.AlertDialog; -import android.app.AlertDialog.Builder; import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.drawable.Drawable; @@ -28,10 +25,11 @@ import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLayoutChangeListener; import android.view.ViewGroup; -import android.view.accessibility.AccessibilityManager; import android.widget.FrameLayout; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.core.view.AccessibilityDelegateCompat; import androidx.core.view.ViewCompat; import androidx.recyclerview.widget.GridLayoutManager.SpanSizeLookup; import androidx.recyclerview.widget.ItemTouchHelper; @@ -49,7 +47,6 @@ import com.android.systemui.qs.customize.TileQueryHelper.TileInfo; import com.android.systemui.qs.customize.TileQueryHelper.TileStateListener; import com.android.systemui.qs.external.CustomTile; import com.android.systemui.qs.tileimpl.QSIconViewImpl; -import com.android.systemui.statusbar.phone.SystemUIDialog; import java.util.ArrayList; import java.util.List; @@ -78,10 +75,10 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private final List<TileInfo> mTiles = new ArrayList<>(); private final ItemTouchHelper mItemTouchHelper; private final ItemDecoration mDecoration; - private final AccessibilityManager mAccessibilityManager; private final int mMinNumTiles; private int mEditIndex; private int mTileDividerIndex; + private int mFocusIndex; private boolean mNeedsFocus; private List<String> mCurrentSpecs; private List<TileInfo> mOtherTiles; @@ -90,17 +87,28 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta private Holder mCurrentDrag; private int mAccessibilityAction = ACTION_NONE; private int mAccessibilityFromIndex; - private CharSequence mAccessibilityFromLabel; private QSTileHost mHost; private final UiEventLogger mUiEventLogger; + private final AccessibilityDelegateCompat mAccessibilityDelegate; + private RecyclerView mRecyclerView; public TileAdapter(Context context, UiEventLogger uiEventLogger) { mContext = context; mUiEventLogger = uiEventLogger; - mAccessibilityManager = context.getSystemService(AccessibilityManager.class); mItemTouchHelper = new ItemTouchHelper(mCallbacks); mDecoration = new TileItemDecoration(context); mMinNumTiles = context.getResources().getInteger(R.integer.quick_settings_min_num_tiles); + mAccessibilityDelegate = new TileAdapterDelegate(); + } + + @Override + public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) { + mRecyclerView = recyclerView; + } + + @Override + public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) { + mRecyclerView = null; } public void setHost(QSTileHost host) { @@ -130,7 +138,6 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta // Remove blank tile from last spot mTiles.remove(--mEditIndex); // Update the tile divider position - mTileDividerIndex--; notifyDataSetChanged(); } mAccessibilityAction = ACTION_NONE; @@ -241,14 +248,12 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta } private void setSelectableForHeaders(View view) { - if (mAccessibilityManager.isTouchExplorationEnabled()) { - final boolean selectable = mAccessibilityAction == ACTION_NONE; - view.setFocusable(selectable); - view.setImportantForAccessibility(selectable - ? View.IMPORTANT_FOR_ACCESSIBILITY_YES - : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - view.setFocusableInTouchMode(selectable); - } + final boolean selectable = mAccessibilityAction == ACTION_NONE; + view.setFocusable(selectable); + view.setImportantForAccessibility(selectable + ? View.IMPORTANT_FOR_ACCESSIBILITY_YES + : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); + view.setFocusableInTouchMode(selectable); } @Override @@ -285,12 +290,11 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta holder.mTileView.setVisibility(View.VISIBLE); holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); holder.mTileView.setContentDescription(mContext.getString( - R.string.accessibility_qs_edit_tile_add, mAccessibilityFromLabel, - position)); + R.string.accessibility_qs_edit_tile_add_to_position, position)); holder.mTileView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - selectPosition(holder.getAdapterPosition(), v); + selectPosition(holder.getLayoutPosition()); } }); focusOnHolder(holder); @@ -299,54 +303,49 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta TileInfo info = mTiles.get(position); - if (position > mEditIndex) { - info.state.contentDescription = mContext.getString( - R.string.accessibility_qs_edit_add_tile_label, info.state.label); - } else if (mAccessibilityAction == ACTION_ADD) { + final boolean selectable = 0 < position && position < mEditIndex; + if (selectable && mAccessibilityAction == ACTION_ADD) { info.state.contentDescription = mContext.getString( - R.string.accessibility_qs_edit_tile_add, mAccessibilityFromLabel, position); - } else if (mAccessibilityAction == ACTION_MOVE) { + R.string.accessibility_qs_edit_tile_add_to_position, position); + } else if (selectable && mAccessibilityAction == ACTION_MOVE) { info.state.contentDescription = mContext.getString( - R.string.accessibility_qs_edit_tile_move, mAccessibilityFromLabel, position); + R.string.accessibility_qs_edit_tile_move_to_position, position); } else { - info.state.contentDescription = mContext.getString( - R.string.accessibility_qs_edit_tile_label, position, info.state.label); + info.state.contentDescription = info.state.label; } + info.state.expandedAccessibilityClassName = ""; + holder.mTileView.handleStateChanged(info.state); holder.mTileView.setShowAppLabel(position > mEditIndex && !info.isSystem); + holder.mTileView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + holder.mTileView.setClickable(true); + holder.mTileView.setOnClickListener(null); + holder.mTileView.setFocusable(true); + holder.mTileView.setFocusableInTouchMode(true); - if (mAccessibilityManager.isTouchExplorationEnabled()) { - final boolean selectable = mAccessibilityAction == ACTION_NONE || position < mEditIndex; + if (mAccessibilityAction != ACTION_NONE) { holder.mTileView.setClickable(selectable); holder.mTileView.setFocusable(selectable); + holder.mTileView.setFocusableInTouchMode(selectable); holder.mTileView.setImportantForAccessibility(selectable ? View.IMPORTANT_FOR_ACCESSIBILITY_YES : View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - holder.mTileView.setFocusableInTouchMode(selectable); if (selectable) { holder.mTileView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - int position = holder.getAdapterPosition(); + int position = holder.getLayoutPosition(); if (position == RecyclerView.NO_POSITION) return; if (mAccessibilityAction != ACTION_NONE) { - selectPosition(position, v); - } else { - if (position < mEditIndex && canRemoveTiles()) { - showAccessibilityDialog(position, v); - } else if (position < mEditIndex && !canRemoveTiles()) { - startAccessibleMove(position); - } else { - startAccessibleAdd(position); - } + selectPosition(position); } } }); - if (position == mAccessibilityFromIndex) { - focusOnHolder(holder); - } } } + if (position == mFocusIndex) { + focusOnHolder(holder); + } } private void focusOnHolder(Holder holder) { @@ -360,9 +359,13 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta int oldLeft, int oldTop, int oldRight, int oldBottom) { holder.mTileView.removeOnLayoutChangeListener(this); holder.mTileView.requestFocus(); + if (mAccessibilityAction == ACTION_NONE) { + holder.mTileView.clearFocus(); + } } }); mNeedsFocus = false; + mFocusIndex = RecyclerView.NO_POSITION; } } @@ -370,72 +373,77 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta return mCurrentSpecs.size() > mMinNumTiles; } - private void selectPosition(int position, View v) { + private void selectPosition(int position) { if (mAccessibilityAction == ACTION_ADD) { // Remove the placeholder. mTiles.remove(mEditIndex--); - notifyItemRemoved(mEditIndex); } mAccessibilityAction = ACTION_NONE; - move(mAccessibilityFromIndex, position, v); + move(mAccessibilityFromIndex, position, false); + mFocusIndex = position; + mNeedsFocus = true; notifyDataSetChanged(); } - private void showAccessibilityDialog(final int position, final View v) { - final TileInfo info = mTiles.get(position); - CharSequence[] options = new CharSequence[] { - mContext.getString(R.string.accessibility_qs_edit_move_tile, info.state.label), - mContext.getString(R.string.accessibility_qs_edit_remove_tile, info.state.label), - }; - AlertDialog dialog = new Builder(mContext) - .setItems(options, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (which == 0) { - startAccessibleMove(position); - } else { - move(position, info.isSystem ? mEditIndex : mTileDividerIndex, v); - notifyItemChanged(mTileDividerIndex); - notifyDataSetChanged(); - } - } - }).setNegativeButton(android.R.string.cancel, null) - .create(); - SystemUIDialog.setShowForAllUsers(dialog, true); - SystemUIDialog.applyFlags(dialog); - dialog.show(); - } - private void startAccessibleAdd(int position) { mAccessibilityFromIndex = position; - mAccessibilityFromLabel = mTiles.get(position).state.label; mAccessibilityAction = ACTION_ADD; // Add placeholder for last slot. mTiles.add(mEditIndex++, null); // Update the tile divider position mTileDividerIndex++; + mFocusIndex = mEditIndex - 1; mNeedsFocus = true; + if (mRecyclerView != null) { + mRecyclerView.post(() -> mRecyclerView.smoothScrollToPosition(mFocusIndex)); + } notifyDataSetChanged(); } private void startAccessibleMove(int position) { mAccessibilityFromIndex = position; - mAccessibilityFromLabel = mTiles.get(position).state.label; mAccessibilityAction = ACTION_MOVE; + mFocusIndex = position; mNeedsFocus = true; notifyDataSetChanged(); } + private boolean canRemoveFromPosition(int position) { + return canRemoveTiles() && isCurrentTile(position); + } + + private boolean isCurrentTile(int position) { + return position < mEditIndex; + } + + private boolean canAddFromPosition(int position) { + return position > mEditIndex; + } + + private void addFromPosition(int position) { + if (!canAddFromPosition(position)) return; + move(position, mEditIndex); + } + + private void removeFromPosition(int position) { + if (!canRemoveFromPosition(position)) return; + TileInfo info = mTiles.get(position); + move(position, info.isSystem ? mEditIndex : mTileDividerIndex); + } + public SpanSizeLookup getSizeLookup() { return mSizeLookup; } - private boolean move(int from, int to, View v) { + private boolean move(int from, int to) { + return move(from, to, true); + } + + private boolean move(int from, int to, boolean notify) { if (to == from) { return true; } - CharSequence fromLabel = mTiles.get(from).state.label; - move(from, to, mTiles); + move(from, to, mTiles, notify); updateDividerLocations(); if (to >= mEditIndex) { mUiEventLogger.log(QSEditEvent.QS_EDIT_REMOVE, 0, strip(mTiles.get(to))); @@ -477,9 +485,11 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta return spec; } - private <T> void move(int from, int to, List<T> list) { + private <T> void move(int from, int to, List<T> list, boolean notify) { list.add(to, list.remove(from)); - notifyItemMoved(from, to); + if (notify) { + notifyItemMoved(from, to); + } } public class Holder extends ViewHolder { @@ -491,6 +501,8 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta mTileView = (CustomizeTileView) ((FrameLayout) itemView).getChildAt(0); mTileView.setBackground(null); mTileView.getIcon().disableAnimation(); + mTileView.setTag(this); + ViewCompat.setAccessibilityDelegate(mTileView, mAccessibilityDelegate); } } @@ -527,6 +539,46 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta .setDuration(DRAG_LENGTH) .alpha(.6f); } + + boolean canRemove() { + return canRemoveFromPosition(getLayoutPosition()); + } + + boolean canAdd() { + return canAddFromPosition(getLayoutPosition()); + } + + void toggleState() { + if (canAdd()) { + add(); + } else { + remove(); + } + } + + private void add() { + addFromPosition(getLayoutPosition()); + } + + private void remove() { + removeFromPosition(getLayoutPosition()); + } + + boolean isCurrentTile() { + return TileAdapter.this.isCurrentTile(getLayoutPosition()); + } + + void startAccessibleAdd() { + TileAdapter.this.startAccessibleAdd(getLayoutPosition()); + } + + void startAccessibleMove() { + TileAdapter.this.startAccessibleMove(getLayoutPosition()); + } + + boolean canTakeAccessibleAction() { + return mAccessibilityAction == ACTION_NONE; + } } private final SpanSizeLookup mSizeLookup = new SpanSizeLookup() { @@ -648,7 +700,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta to == 0 || to == RecyclerView.NO_POSITION) { return false; } - return move(from, to, target.itemView); + return move(from, to); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java new file mode 100644 index 000000000000..1e426adac0b8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapterDelegate.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2020 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.systemui.qs.customize; + +import android.os.Bundle; +import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; + +import androidx.core.view.AccessibilityDelegateCompat; +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; + +import com.android.systemui.R; + +import java.util.List; + +/** + * Accessibility delegate for {@link TileAdapter} views. + * + * This delegate will populate the accessibility info with the proper actions that can be taken for + * the different tiles: + * <ul> + * <li>Add to end if the tile is not a current tile (by double tap).</li> + * <li>Add to a given position (by context menu). This will let the user select a position.</li> + * <li>Remove, if the tile is a current tile (by double tap).</li> + * <li>Move to a given position (by context menu). This will let the user select a position.</li> + * </ul> + * + * This only handles generating the associated actions. The logic for selecting positions is handled + * by {@link TileAdapter}. + * + * In order for the delegate to work properly, the asociated {@link TileAdapter.Holder} should be + * passed along with the view using {@link View#setTag}. + */ +class TileAdapterDelegate extends AccessibilityDelegateCompat { + + private static final int MOVE_TO_POSITION_ID = R.id.accessibility_action_qs_move_to_position; + private static final int ADD_TO_POSITION_ID = R.id.accessibility_action_qs_add_to_position; + + private TileAdapter.Holder getHolder(View view) { + return (TileAdapter.Holder) view.getTag(); + } + + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { + super.onInitializeAccessibilityNodeInfo(host, info); + TileAdapter.Holder holder = getHolder(host); + info.setCollectionItemInfo(null); + info.setStateDescription(""); + if (holder == null || !holder.canTakeAccessibleAction()) { + // If there's not a holder (not a regular Tile) or an action cannot be taken + // because we are in the middle of an accessibility action, don't create a special node. + return; + } + + addClickAction(host, info, holder); + maybeAddActionAddToPosition(host, info, holder); + maybeAddActionMoveToPosition(host, info, holder); + + if (holder.isCurrentTile()) { + info.setStateDescription(host.getContext().getString( + R.string.accessibility_qs_edit_position, holder.getLayoutPosition())); + } + } + + @Override + public boolean performAccessibilityAction(View host, int action, Bundle args) { + TileAdapter.Holder holder = getHolder(host); + + if (holder == null || !holder.canTakeAccessibleAction()) { + // If there's not a holder (not a regular Tile) or an action cannot be taken + // because we are in the middle of an accessibility action, perform the default action. + return super.performAccessibilityAction(host, action, args); + } + if (action == AccessibilityNodeInfo.ACTION_CLICK) { + holder.toggleState(); + return true; + } else if (action == MOVE_TO_POSITION_ID) { + holder.startAccessibleMove(); + return true; + } else if (action == ADD_TO_POSITION_ID) { + holder.startAccessibleAdd(); + return true; + } else { + return super.performAccessibilityAction(host, action, args); + } + } + + private void addClickAction( + View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder) { + String clickActionString; + if (holder.canAdd()) { + clickActionString = host.getContext().getString( + R.string.accessibility_qs_edit_tile_add_action); + } else if (holder.canRemove()) { + clickActionString = host.getContext().getString( + R.string.accessibility_qs_edit_remove_tile_action); + } else { + // Remove the default click action if tile can't either be added or removed (for example + // if there's the minimum number of tiles) + List<AccessibilityNodeInfoCompat.AccessibilityActionCompat> listOfActions = + info.getActionList(); // This is a copy + int numActions = listOfActions.size(); + for (int i = 0; i < numActions; i++) { + if (listOfActions.get(i).getId() == AccessibilityNodeInfo.ACTION_CLICK) { + info.removeAction(listOfActions.get(i)); + } + } + return; + } + + AccessibilityNodeInfoCompat.AccessibilityActionCompat action = + new AccessibilityNodeInfoCompat.AccessibilityActionCompat( + AccessibilityNodeInfo.ACTION_CLICK, clickActionString); + info.addAction(action); + } + + private void maybeAddActionMoveToPosition( + View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder) { + if (holder.isCurrentTile()) { + AccessibilityNodeInfoCompat.AccessibilityActionCompat action = + new AccessibilityNodeInfoCompat.AccessibilityActionCompat(MOVE_TO_POSITION_ID, + host.getContext().getString( + R.string.accessibility_qs_edit_tile_start_move)); + info.addAction(action); + } + } + + private void maybeAddActionAddToPosition( + View host, AccessibilityNodeInfoCompat info, TileAdapter.Holder holder) { + if (holder.canAdd()) { + AccessibilityNodeInfoCompat.AccessibilityActionCompat action = + new AccessibilityNodeInfoCompat.AccessibilityActionCompat(ADD_TO_POSITION_ID, + host.getContext().getString( + R.string.accessibility_qs_edit_tile_start_add)); + info.addAction(action); + } + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java new file mode 100644 index 000000000000..a5dead0f3258 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterDelegateTest.java @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2020 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.systemui.qs.customize; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.fail; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.os.Bundle; +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.view.accessibility.AccessibilityNodeInfo; + +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; +import androidx.test.filters.SmallTest; + +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class TileAdapterDelegateTest extends SysuiTestCase { + + private static final int MOVE_TO_POSITION_ID = R.id.accessibility_action_qs_move_to_position; + private static final int ADD_TO_POSITION_ID = R.id.accessibility_action_qs_add_to_position; + private static final int POSITION_STRING_ID = R.string.accessibility_qs_edit_position; + + @Mock + private TileAdapter.Holder mHolder; + + private AccessibilityNodeInfoCompat mInfo; + private TileAdapterDelegate mDelegate; + private View mView; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mView = new View(mContext); + mDelegate = new TileAdapterDelegate(); + mInfo = AccessibilityNodeInfoCompat.obtain(); + } + + @Test + public void testInfoNoSpecialActionsWhenNoHolder() { + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + for (AccessibilityNodeInfoCompat.AccessibilityActionCompat action : mInfo.getActionList()) { + if (action.getId() == MOVE_TO_POSITION_ID || action.getId() == ADD_TO_POSITION_ID + || action.getId() == AccessibilityNodeInfo.ACTION_CLICK) { + fail("It should not have special action " + action.getId()); + } + } + } + + @Test + public void testInfoNoSpecialActionsWhenCannotStartAccessibleAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(false); + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + for (AccessibilityNodeInfoCompat.AccessibilityActionCompat action : mInfo.getActionList()) { + if (action.getId() == MOVE_TO_POSITION_ID || action.getId() == ADD_TO_POSITION_ID + || action.getId() == AccessibilityNodeInfo.ACTION_CLICK) { + fail("It should not have special action " + action.getId()); + } + } + } + + @Test + public void testNoCollectionItemInfo() { + mInfo.setCollectionItemInfo( + AccessibilityNodeInfoCompat.CollectionItemInfoCompat.obtain(0, 1, 0, 1, false)); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(mInfo.getCollectionItemInfo()).isNull(); + } + + @Test + public void testStateDescriptionHasPositionForCurrentTile() { + mView.setTag(mHolder); + int position = 3; + when(mHolder.getLayoutPosition()).thenReturn(position); + when(mHolder.isCurrentTile()).thenReturn(true); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + + String expectedString = mContext.getString(POSITION_STRING_ID, position); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(mInfo.getStateDescription()).isEqualTo(expectedString); + } + + @Test + public void testStateDescriptionEmptyForNotCurrentTile() { + mView.setTag(mHolder); + int position = 3; + when(mHolder.getLayoutPosition()).thenReturn(position); + when(mHolder.isCurrentTile()).thenReturn(false); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(mInfo.getStateDescription()).isEqualTo(""); + } + + @Test + public void testClickAddAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.canAdd()).thenReturn(true); + when(mHolder.canRemove()).thenReturn(false); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + + String expectedString = mContext.getString(R.string.accessibility_qs_edit_tile_add_action); + AccessibilityNodeInfoCompat.AccessibilityActionCompat action = + getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK); + assertThat(action.getLabel().toString()).contains(expectedString); + } + + @Test + public void testClickRemoveAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.canAdd()).thenReturn(false); + when(mHolder.canRemove()).thenReturn(true); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + + String expectedString = mContext.getString( + R.string.accessibility_qs_edit_remove_tile_action); + AccessibilityNodeInfoCompat.AccessibilityActionCompat action = + getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK); + assertThat(action.getLabel().toString()).contains(expectedString); + } + + @Test + public void testNoClickAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.canAdd()).thenReturn(false); + when(mHolder.canRemove()).thenReturn(false); + mInfo.addAction(AccessibilityNodeInfo.ACTION_CLICK); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + + AccessibilityNodeInfoCompat.AccessibilityActionCompat action = + getActionForId(mInfo, AccessibilityNodeInfo.ACTION_CLICK); + assertThat(action).isNull(); + } + + @Test + public void testAddToPositionAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.canAdd()).thenReturn(true); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(getActionForId(mInfo, ADD_TO_POSITION_ID)).isNotNull(); + } + + @Test + public void testNoAddToPositionAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.canAdd()).thenReturn(false); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(getActionForId(mInfo, ADD_TO_POSITION_ID)).isNull(); + } + + @Test + public void testMoveToPositionAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.isCurrentTile()).thenReturn(true); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(getActionForId(mInfo, MOVE_TO_POSITION_ID)).isNotNull(); + } + + @Test + public void testNoMoveToPositionAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + when(mHolder.isCurrentTile()).thenReturn(false); + + mDelegate.onInitializeAccessibilityNodeInfo(mView, mInfo); + assertThat(getActionForId(mInfo, MOVE_TO_POSITION_ID)).isNull(); + } + + @Test + public void testNoInteractionsWhenCannotTakeAccessibleAction() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(false); + + mDelegate.performAccessibilityAction(mView, AccessibilityNodeInfo.ACTION_CLICK, null); + mDelegate.performAccessibilityAction(mView, MOVE_TO_POSITION_ID, new Bundle()); + mDelegate.performAccessibilityAction(mView, ADD_TO_POSITION_ID, new Bundle()); + + verify(mHolder, never()).toggleState(); + verify(mHolder, never()).startAccessibleAdd(); + verify(mHolder, never()).startAccessibleMove(); + } + + @Test + public void testClickActionTogglesState() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + + mDelegate.performAccessibilityAction(mView, AccessibilityNodeInfo.ACTION_CLICK, null); + + verify(mHolder).toggleState(); + } + + @Test + public void testAddToPositionActionStartsAccessibleAdd() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + + mDelegate.performAccessibilityAction(mView, ADD_TO_POSITION_ID, null); + + verify(mHolder).startAccessibleAdd(); + } + + @Test + public void testMoveToPositionActionStartsAccessibleMove() { + mView.setTag(mHolder); + when(mHolder.canTakeAccessibleAction()).thenReturn(true); + + mDelegate.performAccessibilityAction(mView, MOVE_TO_POSITION_ID, null); + + verify(mHolder).startAccessibleMove(); + } + + private AccessibilityNodeInfoCompat.AccessibilityActionCompat getActionForId( + AccessibilityNodeInfoCompat info, int action) { + for (AccessibilityNodeInfoCompat.AccessibilityActionCompat a : info.getActionList()) { + if (a.getId() == action) { + return a; + } + } + return null; + } +} |