blob: 70c5a648478cc2d8241cedf8986ed45a704ced7d [file] [log] [blame]
Garfield Tan84bd0f12016-09-12 14:18:32 -07001/*
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.documentsui.dirlist;
18
Steve McKayd9caa6a2016-09-15 16:36:45 -070019import static com.android.documentsui.base.Shared.DEBUG;
Garfield Tan84bd0f12016-09-12 14:18:32 -070020
21import android.annotation.IdRes;
22import android.annotation.Nullable;
23import android.app.Activity;
24import android.content.Context;
25import android.text.TextUtils;
26import android.util.Log;
27import android.view.ActionMode;
28import android.view.HapticFeedbackConstants;
29import android.view.Menu;
30import android.view.MenuItem;
31import android.view.View;
32
33import com.android.documentsui.MenuManager;
Garfield Tan84bd0f12016-09-12 14:18:32 -070034import com.android.documentsui.R;
Steve McKaybff980a2016-09-13 17:39:50 -070035import com.android.documentsui.base.EventHandler;
Steve McKayd9caa6a2016-09-15 16:36:45 -070036import com.android.documentsui.base.Menus;
37import com.android.documentsui.base.Shared;
Garfield Tan84bd0f12016-09-12 14:18:32 -070038import com.android.documentsui.dirlist.MultiSelectManager.Selection;
39
40import java.util.function.Consumer;
41import java.util.function.Function;
42import java.util.function.IntConsumer;
43
44/**
45 * A controller that listens to selection changes and manages life cycles of action modes.
46 */
47class ActionModeController implements MultiSelectManager.Callback, ActionMode.Callback {
48
49 private static final String TAG = "ActionModeController";
50
51 private final Context mContext;
52 private final MultiSelectManager mSelectionMgr;
53 private final MenuManager mMenuManager;
54 private final MenuManager.SelectionDetails mSelectionDetails;
55
56 private final Function<ActionMode.Callback, ActionMode> mActionModeFactory;
57 private final EventHandler<MenuItem> mMenuItemClicker;
58 private final IntConsumer mHapticPerformer;
59 private final Consumer<CharSequence> mAccessibilityAnnouncer;
60 private final AccessibilityImportanceSetter mAccessibilityImportanceSetter;
61
62 private final Selection mSelected = new Selection();
63
64 private @Nullable ActionMode mActionMode;
65 private @Nullable Menu mMenu;
66
67 private ActionModeController(
68 Context context,
69 MultiSelectManager selectionMgr,
70 MenuManager menuManager,
71 MenuManager.SelectionDetails selectionDetails,
72 Function<ActionMode.Callback, ActionMode> actionModeFactory,
73 EventHandler<MenuItem> menuItemClicker,
74 IntConsumer hapticPerformer,
75 Consumer<CharSequence> accessibilityAnnouncer,
76 AccessibilityImportanceSetter accessibilityImportanceSetter) {
77 mContext = context;
78 mSelectionMgr = selectionMgr;
79 mMenuManager = menuManager;
80 mSelectionDetails = selectionDetails;
81
82 mActionModeFactory = actionModeFactory;
83 mMenuItemClicker = menuItemClicker;
84 mHapticPerformer = hapticPerformer;
85 mAccessibilityAnnouncer = accessibilityAnnouncer;
86 mAccessibilityImportanceSetter = accessibilityImportanceSetter;
87 }
88
89 @Override
90 public void onSelectionChanged() {
91 mSelectionMgr.getSelection(mSelected);
92 if (mSelected.size() > 0) {
93 if (mActionMode == null) {
94 if (DEBUG) Log.d(TAG, "Starting action mode.");
95 mActionMode = mActionModeFactory.apply(this);
96 mHapticPerformer.accept(HapticFeedbackConstants.LONG_PRESS);
97 }
98 updateActionMenu();
99 } else {
100 if (mActionMode != null) {
101 if (DEBUG) Log.d(TAG, "Finishing action mode.");
102 mActionMode.finish();
103 }
104 }
105
106 if (mActionMode != null) {
107 assert(!mSelected.isEmpty());
108 final String title = Shared.getQuantityString(mContext,
109 R.plurals.elements_selected, mSelected.size());
110 mActionMode.setTitle(title);
111 mAccessibilityAnnouncer.accept(title);
112 }
113 }
114
115 @Override
116 public void onSelectionRestored() {
117 mSelectionMgr.getSelection(mSelected);
118 if (mSelected.size() > 0) {
119 if (mActionMode == null) {
120 if (DEBUG) Log.d(TAG, "Starting action mode.");
121 mActionMode = mActionModeFactory.apply(this);
122 }
123 updateActionMenu();
124 } else {
125 if (mActionMode != null) {
126 if (DEBUG) Log.d(TAG, "Finishing action mode.");
127 mActionMode.finish();
128 }
129 }
130
131 if (mActionMode != null) {
132 assert(!mSelected.isEmpty());
133 final String title = Shared.getQuantityString(mContext,
134 R.plurals.elements_selected, mSelected.size());
135 mActionMode.setTitle(title);
136 mAccessibilityAnnouncer.accept(title);
137 }
138 }
139
140 void finishActionMode() {
141 if (mActionMode != null) {
142 mActionMode.finish();
143 mActionMode = null;
144 } else {
145 Log.w(TAG, "Tried to finish a null action mode.");
146 }
147 }
148
149 // Called when the user exits the action mode
150 @Override
151 public void onDestroyActionMode(ActionMode mode) {
152 if (DEBUG) Log.d(TAG, "Handling action mode destroyed.");
153 mActionMode = null;
154 // clear selection
155 mSelectionMgr.clearSelection();
156 mSelected.clear();
157
158 // Re-enable TalkBack for the toolbars, as they are no longer covered by action mode.
159 mAccessibilityImportanceSetter.setAccessibilityImportance(
160 View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, R.id.toolbar, R.id.roots_toolbar);
161 }
162
163 @Override
164 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
165 int size = mSelectionMgr.getSelection().size();
166 mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
167 mode.setTitle(TextUtils.formatSelectedCount(size));
168
169 if (size > 0) {
170
171 // Hide the toolbars if action mode is enabled, so TalkBack doesn't navigate to
172 // these controls when using linear navigation.
173 mAccessibilityImportanceSetter.setAccessibilityImportance(
174 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
175 R.id.toolbar,
176 R.id.roots_toolbar);
177 return true;
178 }
179
180 return false;
181 }
182
183 @Override
184 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
185 mMenu = menu;
186 updateActionMenu();
187 return true;
188 }
189
190 private void updateActionMenu() {
191 assert(mMenu != null);
192 mMenuManager.updateActionMenu(mMenu, mSelectionDetails);
193 Menus.disableHiddenItems(mMenu);
194 }
195
196 @Override
197 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
198 return mMenuItemClicker.apply(item);
199 }
200
201 static ActionModeController create(
202 Context context,
203 MultiSelectManager selectionMgr,
204 MenuManager menuManager,
205 MenuManager.SelectionDetails selectionDetails,
206 Activity activity,
207 View view,
208 EventHandler<MenuItem> menuItemClicker) {
209 return new ActionModeController(
210 context,
211 selectionMgr,
212 menuManager,
213 selectionDetails,
214 activity::startActionMode,
215 menuItemClicker,
216 view::performHapticFeedback,
217 view::announceForAccessibility,
218 (int accessibilityImportance, @IdRes int[] viewIds) -> {
219 setImportantForAccessibility(activity, accessibilityImportance, viewIds);
220 });
221 }
222
223 private static void setImportantForAccessibility(
224 Activity activity, int accessibilityImportance, @IdRes int[] viewIds) {
225 for (final int id : viewIds) {
226 final View v = activity.findViewById(id);
227 if (v != null) {
228 v.setImportantForAccessibility(accessibilityImportance);
229 }
230 }
231 }
232
233 @FunctionalInterface
234 private interface AccessibilityImportanceSetter {
235 void setAccessibilityImportance(int accessibilityImportance, @IdRes int... viewIds);
236 }
237}