blob: c19f35dbff0cfd6d8eeb3862564df213966b6aea [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
Steve McKaybd9f05a2016-10-10 10:18:36 -070017package com.android.documentsui;
Garfield Tan84bd0f12016-09-12 14:18:32 -070018
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
Steve McKay5b0a2c12016-10-07 11:22:31 -070032import com.android.documentsui.MenuManager.SelectionDetails;
Steve McKayc8889af2016-09-23 11:22:41 -070033import com.android.documentsui.base.ConfirmationCallback;
34import com.android.documentsui.base.ConfirmationCallback.Result;
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;
Steve McKay4f78ba62016-10-04 16:48:49 -070037import com.android.documentsui.selection.Selection;
38import com.android.documentsui.selection.SelectionManager;
Steve McKay5b0a2c12016-10-07 11:22:31 -070039import com.android.documentsui.ui.MessageBuilder;
Garfield Tan84bd0f12016-09-12 14:18:32 -070040
41import java.util.function.Consumer;
Garfield Tan84bd0f12016-09-12 14:18:32 -070042import java.util.function.IntConsumer;
43
44/**
45 * A controller that listens to selection changes and manages life cycles of action modes.
46 */
Steve McKaybd9f05a2016-10-10 10:18:36 -070047public class ActionModeController
48 implements SelectionManager.Callback, ActionMode.Callback, ActionModeAddons {
Garfield Tan84bd0f12016-09-12 14:18:32 -070049
50 private static final String TAG = "ActionModeController";
51
Steve McKay5b0a2c12016-10-07 11:22:31 -070052 private final Activity mActivity;
Steve McKay4f78ba62016-10-04 16:48:49 -070053 private final SelectionManager mSelectionMgr;
Garfield Tan84bd0f12016-09-12 14:18:32 -070054 private final MenuManager mMenuManager;
Steve McKay5b0a2c12016-10-07 11:22:31 -070055 private final MessageBuilder mMessages;
Garfield Tan84bd0f12016-09-12 14:18:32 -070056
Steve McKaybd9f05a2016-10-10 10:18:36 -070057 private final ContentScope mScope = new ContentScope();
Garfield Tan84bd0f12016-09-12 14:18:32 -070058 private final Selection mSelected = new Selection();
59
60 private @Nullable ActionMode mActionMode;
61 private @Nullable Menu mMenu;
62
Steve McKay5b0a2c12016-10-07 11:22:31 -070063 public ActionModeController(
64 Activity activity,
Steve McKay4f78ba62016-10-04 16:48:49 -070065 SelectionManager selectionMgr,
Garfield Tan84bd0f12016-09-12 14:18:32 -070066 MenuManager menuManager,
Steve McKay5b0a2c12016-10-07 11:22:31 -070067 MessageBuilder messages) {
68
69 mActivity = activity;
Garfield Tan84bd0f12016-09-12 14:18:32 -070070 mSelectionMgr = selectionMgr;
71 mMenuManager = menuManager;
Steve McKay5b0a2c12016-10-07 11:22:31 -070072 mMessages = messages;
Garfield Tan84bd0f12016-09-12 14:18:32 -070073 }
74
75 @Override
76 public void onSelectionChanged() {
77 mSelectionMgr.getSelection(mSelected);
78 if (mSelected.size() > 0) {
79 if (mActionMode == null) {
80 if (DEBUG) Log.d(TAG, "Starting action mode.");
Steve McKay5b0a2c12016-10-07 11:22:31 -070081 mActionMode = mActivity.startActionMode(this);
Steve McKaybd9f05a2016-10-10 10:18:36 -070082 mScope.hapticPerformer.accept(HapticFeedbackConstants.LONG_PRESS);
Garfield Tan84bd0f12016-09-12 14:18:32 -070083 }
84 updateActionMenu();
85 } else {
86 if (mActionMode != null) {
87 if (DEBUG) Log.d(TAG, "Finishing action mode.");
88 mActionMode.finish();
89 }
90 }
91
92 if (mActionMode != null) {
93 assert(!mSelected.isEmpty());
Steve McKay5b0a2c12016-10-07 11:22:31 -070094 final String title = mMessages.getQuantityString(
Garfield Tan84bd0f12016-09-12 14:18:32 -070095 R.plurals.elements_selected, mSelected.size());
96 mActionMode.setTitle(title);
Ben Linc8739d92016-11-02 11:54:53 -070097 mActivity.getWindow().setTitle(title);
Garfield Tan84bd0f12016-09-12 14:18:32 -070098 }
99 }
100
101 @Override
102 public void onSelectionRestored() {
103 mSelectionMgr.getSelection(mSelected);
104 if (mSelected.size() > 0) {
105 if (mActionMode == null) {
106 if (DEBUG) Log.d(TAG, "Starting action mode.");
Steve McKay5b0a2c12016-10-07 11:22:31 -0700107 mActionMode = mActivity.startActionMode(this);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700108 }
109 updateActionMenu();
110 } else {
111 if (mActionMode != null) {
112 if (DEBUG) Log.d(TAG, "Finishing action mode.");
113 mActionMode.finish();
114 }
115 }
116
117 if (mActionMode != null) {
118 assert(!mSelected.isEmpty());
Steve McKay5b0a2c12016-10-07 11:22:31 -0700119 final String title = mMessages.getQuantityString(
Garfield Tan84bd0f12016-09-12 14:18:32 -0700120 R.plurals.elements_selected, mSelected.size());
121 mActionMode.setTitle(title);
Ben Linc8739d92016-11-02 11:54:53 -0700122 mActivity.getWindow().setTitle(title);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700123 }
124 }
125
126 // Called when the user exits the action mode
127 @Override
128 public void onDestroyActionMode(ActionMode mode) {
Steve McKay4f78ba62016-10-04 16:48:49 -0700129 if (mActionMode == null) {
130 if (DEBUG) Log.w(TAG, "Received call to destroy action mode on alien mode object.");
131 }
132
133 assert(mActionMode.equals(mode));
134
Garfield Tan84bd0f12016-09-12 14:18:32 -0700135 if (DEBUG) Log.d(TAG, "Handling action mode destroyed.");
136 mActionMode = null;
Steve McKay4f78ba62016-10-04 16:48:49 -0700137 mMenu = null;
138
Garfield Tan84bd0f12016-09-12 14:18:32 -0700139 // clear selection
140 mSelectionMgr.clearSelection();
141 mSelected.clear();
142
143 // Re-enable TalkBack for the toolbars, as they are no longer covered by action mode.
Steve McKaybd9f05a2016-10-10 10:18:36 -0700144 mScope.accessibilityImportanceSetter.setAccessibilityImportance(
Garfield Tan84bd0f12016-09-12 14:18:32 -0700145 View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, R.id.toolbar, R.id.roots_toolbar);
146 }
147
148 @Override
149 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
150 int size = mSelectionMgr.getSelection().size();
151 mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
152 mode.setTitle(TextUtils.formatSelectedCount(size));
153
154 if (size > 0) {
155
156 // Hide the toolbars if action mode is enabled, so TalkBack doesn't navigate to
157 // these controls when using linear navigation.
Steve McKaybd9f05a2016-10-10 10:18:36 -0700158 mScope.accessibilityImportanceSetter.setAccessibilityImportance(
Garfield Tan84bd0f12016-09-12 14:18:32 -0700159 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
160 R.id.toolbar,
161 R.id.roots_toolbar);
162 return true;
163 }
164
165 return false;
166 }
167
168 @Override
169 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
170 mMenu = menu;
171 updateActionMenu();
172 return true;
173 }
174
175 private void updateActionMenu() {
176 assert(mMenu != null);
Steve McKaybd9f05a2016-10-10 10:18:36 -0700177 mMenuManager.updateActionMenu(mMenu, mScope.selectionDetails);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700178 Menus.disableHiddenItems(mMenu);
179 }
180
181 @Override
182 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Steve McKaybd9f05a2016-10-10 10:18:36 -0700183 return mScope.menuItemClicker.accept(item);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700184 }
185
186 private static void setImportantForAccessibility(
187 Activity activity, int accessibilityImportance, @IdRes int[] viewIds) {
188 for (final int id : viewIds) {
189 final View v = activity.findViewById(id);
190 if (v != null) {
191 v.setImportantForAccessibility(accessibilityImportance);
192 }
193 }
194 }
195
196 @FunctionalInterface
197 private interface AccessibilityImportanceSetter {
198 void setAccessibilityImportance(int accessibilityImportance, @IdRes int... viewIds);
199 }
Steve McKayc8889af2016-09-23 11:22:41 -0700200
Steve McKaybd9f05a2016-10-10 10:18:36 -0700201 @Override
202 public void finishActionMode() {
203 if (mActionMode != null) {
204 mActionMode.finish();
205 mActionMode = null;
206 } else {
207 Log.w(TAG, "Tried to finish a null action mode.");
208 }
209 }
210
211 @Override
Steve McKayc8889af2016-09-23 11:22:41 -0700212 public void finishOnConfirmed(@Result int code) {
213 if (code == ConfirmationCallback.CONFIRM) {
214 finishActionMode();
215 }
216 }
Steve McKay5b0a2c12016-10-07 11:22:31 -0700217
218 public ActionModeController reset(
219 SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker, View view) {
220 assert(mActionMode == null);
221 assert(mMenu == null);
222
Steve McKaybd9f05a2016-10-10 10:18:36 -0700223 mScope.menuItemClicker = menuItemClicker;
224 mScope.selectionDetails = selectionDetails;
225 mScope.hapticPerformer = view::performHapticFeedback;
Steve McKaybd9f05a2016-10-10 10:18:36 -0700226 mScope.accessibilityImportanceSetter =
Steve McKay5b0a2c12016-10-07 11:22:31 -0700227 (int accessibilityImportance, @IdRes int[] viewIds) -> {
228 setImportantForAccessibility(
229 mActivity, accessibilityImportance, viewIds);
230 };
231
232 return this;
233 }
234
Steve McKaybd9f05a2016-10-10 10:18:36 -0700235 private static final class ContentScope {
Steve McKay5b0a2c12016-10-07 11:22:31 -0700236 private EventHandler<MenuItem> menuItemClicker;
237 private SelectionDetails selectionDetails;
238 private IntConsumer hapticPerformer;
Steve McKay5b0a2c12016-10-07 11:22:31 -0700239 private AccessibilityImportanceSetter accessibilityImportanceSetter;
240 }
Garfield Tan84bd0f12016-09-12 14:18:32 -0700241}