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