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