blob: 03582f4bc6806aae09eff5290bb355ba37d8d0fa [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;
Garfield Tan84bd0f12016-09-12 14:18:32 -070024import android.text.TextUtils;
25import android.util.Log;
26import android.view.ActionMode;
27import android.view.HapticFeedbackConstants;
28import android.view.Menu;
29import android.view.MenuItem;
30import android.view.View;
31
32import com.android.documentsui.MenuManager;
Steve McKay5b0a2c12016-10-07 11:22:31 -070033import com.android.documentsui.MenuManager.SelectionDetails;
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;
Steve McKay4f78ba62016-10-04 16:48:49 -070039import com.android.documentsui.selection.Selection;
40import com.android.documentsui.selection.SelectionManager;
Steve McKay5b0a2c12016-10-07 11:22:31 -070041import com.android.documentsui.ui.MessageBuilder;
Garfield Tan84bd0f12016-09-12 14:18:32 -070042
43import java.util.function.Consumer;
Garfield Tan84bd0f12016-09-12 14:18:32 -070044import java.util.function.IntConsumer;
45
46/**
47 * A controller that listens to selection changes and manages life cycles of action modes.
48 */
Steve McKay4f78ba62016-10-04 16:48:49 -070049public class ActionModeController implements SelectionManager.Callback, ActionMode.Callback {
Garfield Tan84bd0f12016-09-12 14:18:32 -070050
51 private static final String TAG = "ActionModeController";
52
Steve McKay5b0a2c12016-10-07 11:22:31 -070053 private final Activity mActivity;
Steve McKay4f78ba62016-10-04 16:48:49 -070054 private final SelectionManager mSelectionMgr;
Garfield Tan84bd0f12016-09-12 14:18:32 -070055 private final MenuManager mMenuManager;
Steve McKay5b0a2c12016-10-07 11:22:31 -070056 private final MessageBuilder mMessages;
Garfield Tan84bd0f12016-09-12 14:18:32 -070057
Steve McKay5b0a2c12016-10-07 11:22:31 -070058 private final Config mConfig = new Config();
Garfield Tan84bd0f12016-09-12 14:18:32 -070059 private final Selection mSelected = new Selection();
60
61 private @Nullable ActionMode mActionMode;
62 private @Nullable Menu mMenu;
63
Steve McKay5b0a2c12016-10-07 11:22:31 -070064 public ActionModeController(
65 Activity activity,
Steve McKay4f78ba62016-10-04 16:48:49 -070066 SelectionManager selectionMgr,
Garfield Tan84bd0f12016-09-12 14:18:32 -070067 MenuManager menuManager,
Steve McKay5b0a2c12016-10-07 11:22:31 -070068 MessageBuilder messages) {
69
70 mActivity = activity;
Garfield Tan84bd0f12016-09-12 14:18:32 -070071 mSelectionMgr = selectionMgr;
72 mMenuManager = menuManager;
Steve McKay5b0a2c12016-10-07 11:22:31 -070073 mMessages = messages;
Garfield Tan84bd0f12016-09-12 14:18:32 -070074 }
75
76 @Override
77 public void onSelectionChanged() {
78 mSelectionMgr.getSelection(mSelected);
79 if (mSelected.size() > 0) {
80 if (mActionMode == null) {
81 if (DEBUG) Log.d(TAG, "Starting action mode.");
Steve McKay5b0a2c12016-10-07 11:22:31 -070082 mActionMode = mActivity.startActionMode(this);
83 mConfig.hapticPerformer.accept(HapticFeedbackConstants.LONG_PRESS);
Garfield Tan84bd0f12016-09-12 14:18:32 -070084 }
85 updateActionMenu();
86 } else {
87 if (mActionMode != null) {
88 if (DEBUG) Log.d(TAG, "Finishing action mode.");
89 mActionMode.finish();
90 }
91 }
92
93 if (mActionMode != null) {
94 assert(!mSelected.isEmpty());
Steve McKay5b0a2c12016-10-07 11:22:31 -070095 final String title = mMessages.getQuantityString(
Garfield Tan84bd0f12016-09-12 14:18:32 -070096 R.plurals.elements_selected, mSelected.size());
97 mActionMode.setTitle(title);
Steve McKay5b0a2c12016-10-07 11:22:31 -070098 mConfig.accessibilityAnnouncer.accept(title);
Garfield Tan84bd0f12016-09-12 14:18:32 -070099 }
100 }
101
102 @Override
103 public void onSelectionRestored() {
104 mSelectionMgr.getSelection(mSelected);
105 if (mSelected.size() > 0) {
106 if (mActionMode == null) {
107 if (DEBUG) Log.d(TAG, "Starting action mode.");
Steve McKay5b0a2c12016-10-07 11:22:31 -0700108 mActionMode = mActivity.startActionMode(this);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700109 }
110 updateActionMenu();
111 } else {
112 if (mActionMode != null) {
113 if (DEBUG) Log.d(TAG, "Finishing action mode.");
114 mActionMode.finish();
115 }
116 }
117
118 if (mActionMode != null) {
119 assert(!mSelected.isEmpty());
Steve McKay5b0a2c12016-10-07 11:22:31 -0700120 final String title = mMessages.getQuantityString(
Garfield Tan84bd0f12016-09-12 14:18:32 -0700121 R.plurals.elements_selected, mSelected.size());
122 mActionMode.setTitle(title);
Steve McKay5b0a2c12016-10-07 11:22:31 -0700123 mConfig.accessibilityAnnouncer.accept(title);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700124 }
125 }
126
127 void finishActionMode() {
128 if (mActionMode != null) {
129 mActionMode.finish();
130 mActionMode = null;
131 } else {
132 Log.w(TAG, "Tried to finish a null action mode.");
133 }
134 }
135
136 // Called when the user exits the action mode
137 @Override
138 public void onDestroyActionMode(ActionMode mode) {
Steve McKay4f78ba62016-10-04 16:48:49 -0700139 if (mActionMode == null) {
140 if (DEBUG) Log.w(TAG, "Received call to destroy action mode on alien mode object.");
141 }
142
143 assert(mActionMode.equals(mode));
144
Garfield Tan84bd0f12016-09-12 14:18:32 -0700145 if (DEBUG) Log.d(TAG, "Handling action mode destroyed.");
146 mActionMode = null;
Steve McKay4f78ba62016-10-04 16:48:49 -0700147 mMenu = null;
148
Garfield Tan84bd0f12016-09-12 14:18:32 -0700149 // clear selection
150 mSelectionMgr.clearSelection();
151 mSelected.clear();
152
153 // Re-enable TalkBack for the toolbars, as they are no longer covered by action mode.
Steve McKay5b0a2c12016-10-07 11:22:31 -0700154 mConfig.accessibilityImportanceSetter.setAccessibilityImportance(
Garfield Tan84bd0f12016-09-12 14:18:32 -0700155 View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, R.id.toolbar, R.id.roots_toolbar);
156 }
157
158 @Override
159 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
160 int size = mSelectionMgr.getSelection().size();
161 mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
162 mode.setTitle(TextUtils.formatSelectedCount(size));
163
164 if (size > 0) {
165
166 // Hide the toolbars if action mode is enabled, so TalkBack doesn't navigate to
167 // these controls when using linear navigation.
Steve McKay5b0a2c12016-10-07 11:22:31 -0700168 mConfig.accessibilityImportanceSetter.setAccessibilityImportance(
Garfield Tan84bd0f12016-09-12 14:18:32 -0700169 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
170 R.id.toolbar,
171 R.id.roots_toolbar);
172 return true;
173 }
174
175 return false;
176 }
177
178 @Override
179 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
180 mMenu = menu;
181 updateActionMenu();
182 return true;
183 }
184
185 private void updateActionMenu() {
186 assert(mMenu != null);
Steve McKay5b0a2c12016-10-07 11:22:31 -0700187 mMenuManager.updateActionMenu(mMenu, mConfig.selectionDetails);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700188 Menus.disableHiddenItems(mMenu);
189 }
190
191 @Override
192 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Steve McKay5b0a2c12016-10-07 11:22:31 -0700193 return mConfig.menuItemClicker.accept(item);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700194 }
195
196 private static void setImportantForAccessibility(
197 Activity activity, int accessibilityImportance, @IdRes int[] viewIds) {
198 for (final int id : viewIds) {
199 final View v = activity.findViewById(id);
200 if (v != null) {
201 v.setImportantForAccessibility(accessibilityImportance);
202 }
203 }
204 }
205
206 @FunctionalInterface
207 private interface AccessibilityImportanceSetter {
208 void setAccessibilityImportance(int accessibilityImportance, @IdRes int... viewIds);
209 }
Steve McKayc8889af2016-09-23 11:22:41 -0700210
211 public void finishOnConfirmed(@Result int code) {
212 if (code == ConfirmationCallback.CONFIRM) {
213 finishActionMode();
214 }
215 }
Steve McKay5b0a2c12016-10-07 11:22:31 -0700216
217 public ActionModeController reset(
218 SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker, View view) {
219 assert(mActionMode == null);
220 assert(mMenu == null);
221
222 mConfig.menuItemClicker = menuItemClicker;
223 mConfig.selectionDetails = selectionDetails;
224 mConfig.hapticPerformer = view::performHapticFeedback;
225 mConfig.accessibilityAnnouncer = view::announceForAccessibility;
226 mConfig.accessibilityImportanceSetter =
227 (int accessibilityImportance, @IdRes int[] viewIds) -> {
228 setImportantForAccessibility(
229 mActivity, accessibilityImportance, viewIds);
230 };
231
232 return this;
233 }
234
235 private static final class Config {
236 private EventHandler<MenuItem> menuItemClicker;
237 private SelectionDetails selectionDetails;
238 private IntConsumer hapticPerformer;
239 private Consumer<CharSequence> accessibilityAnnouncer;
240 private AccessibilityImportanceSetter accessibilityImportanceSetter;
241 }
Garfield Tan84bd0f12016-09-12 14:18:32 -0700242}