blob: 7548250065ce5ee38220ba126e360892cf3099a8 [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
Felipe Leme9de58072018-01-19 16:40:04 -080019import static com.android.documentsui.base.SharedMinimal.DEBUG;
Garfield Tan84bd0f12016-09-12 14:18:32 -070020
Jeff Sharkeya4ff00f2018-07-09 14:57:51 -060021import androidx.annotation.IdRes;
22import androidx.annotation.Nullable;
Garfield Tan84bd0f12016-09-12 14:18:32 -070023import android.app.Activity;
Garfield Tan84bd0f12016-09-12 14:18:32 -070024import android.text.TextUtils;
25import android.util.Log;
26import android.view.ActionMode;
Garfield Tan84bd0f12016-09-12 14:18:32 -070027import android.view.Menu;
28import android.view.MenuItem;
29import android.view.View;
30
Steve McKay5b0a2c12016-10-07 11:22:31 -070031import com.android.documentsui.MenuManager.SelectionDetails;
shawnlin48200cd2018-10-16 16:20:42 +080032import com.android.documentsui.base.ConfirmationCallback;
33import com.android.documentsui.base.ConfirmationCallback.Result;
Steve McKaybff980a2016-09-13 17:39:50 -070034import com.android.documentsui.base.EventHandler;
Steve McKayd9caa6a2016-09-15 16:36:45 -070035import com.android.documentsui.base.Menus;
Steve McKay5b0a2c12016-10-07 11:22:31 -070036import com.android.documentsui.ui.MessageBuilder;
Garfield Tan84bd0f12016-09-12 14:18:32 -070037
Riddle Hsu0c375982018-06-21 22:06:43 +080038import androidx.recyclerview.selection.MutableSelection;
39import androidx.recyclerview.selection.SelectionTracker;
40import androidx.recyclerview.selection.SelectionTracker.SelectionObserver;
41
Garfield Tan84bd0f12016-09-12 14:18:32 -070042/**
43 * A controller that listens to selection changes and manages life cycles of action modes.
44 */
Riddle Hsu0c375982018-06-21 22:06:43 +080045public class ActionModeController extends SelectionObserver<String>
Steve McKay365e3cb2017-08-31 10:27:08 -070046 implements ActionMode.Callback, ActionModeAddons {
Garfield Tan84bd0f12016-09-12 14:18:32 -070047
48 private static final String TAG = "ActionModeController";
49
Steve McKay5b0a2c12016-10-07 11:22:31 -070050 private final Activity mActivity;
Riddle Hsu0c375982018-06-21 22:06:43 +080051 private final SelectionTracker<String> mSelectionMgr;
Garfield Tan84bd0f12016-09-12 14:18:32 -070052 private final MenuManager mMenuManager;
Steve McKay5b0a2c12016-10-07 11:22:31 -070053 private final MessageBuilder mMessages;
Garfield Tan84bd0f12016-09-12 14:18:32 -070054
Steve McKaybd9f05a2016-10-10 10:18:36 -070055 private final ContentScope mScope = new ContentScope();
Riddle Hsu0c375982018-06-21 22:06:43 +080056 private final MutableSelection<String> mSelected = new MutableSelection<>();
Garfield Tan84bd0f12016-09-12 14:18:32 -070057
58 private @Nullable ActionMode mActionMode;
59 private @Nullable Menu mMenu;
60
Steve McKay5b0a2c12016-10-07 11:22:31 -070061 public ActionModeController(
62 Activity activity,
Riddle Hsu0c375982018-06-21 22:06:43 +080063 SelectionTracker<String> selectionMgr,
Garfield Tan84bd0f12016-09-12 14:18:32 -070064 MenuManager menuManager,
Steve McKay5b0a2c12016-10-07 11:22:31 -070065 MessageBuilder messages) {
66
67 mActivity = activity;
Garfield Tan84bd0f12016-09-12 14:18:32 -070068 mSelectionMgr = selectionMgr;
69 mMenuManager = menuManager;
Steve McKay5b0a2c12016-10-07 11:22:31 -070070 mMessages = messages;
Garfield Tan84bd0f12016-09-12 14:18:32 -070071 }
72
73 @Override
74 public void onSelectionChanged() {
Steve McKay2d19a692017-08-22 09:05:08 -070075 mSelectionMgr.copySelection(mSelected);
Garfield Tan84bd0f12016-09-12 14:18:32 -070076 if (mSelected.size() > 0) {
77 if (mActionMode == null) {
Jason Chang96f886b2019-03-29 17:59:02 +080078 if (DEBUG) {
79 Log.d(TAG, "Starting action mode.");
80 }
Steve McKay5b0a2c12016-10-07 11:22:31 -070081 mActionMode = mActivity.startActionMode(this);
Garfield Tan84bd0f12016-09-12 14:18:32 -070082 }
83 updateActionMenu();
84 } else {
85 if (mActionMode != null) {
Jason Chang96f886b2019-03-29 17:59:02 +080086 if (DEBUG) {
87 Log.d(TAG, "Finishing action mode.");
88 }
Garfield Tan84bd0f12016-09-12 14:18:32 -070089 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);
Ben Linc8739d92016-11-02 11:54:53 -070098 mActivity.getWindow().setTitle(title);
Garfield Tan84bd0f12016-09-12 14:18:32 -070099 }
100 }
101
102 @Override
103 public void onSelectionRestored() {
Steve McKay365e3cb2017-08-31 10:27:08 -0700104 onSelectionChanged();
Steve McKaye77c0372017-08-18 14:29:40 -0700105 }
106
Garfield Tan84bd0f12016-09-12 14:18:32 -0700107 // Called when the user exits the action mode
108 @Override
109 public void onDestroyActionMode(ActionMode mode) {
Steve McKay4f78ba62016-10-04 16:48:49 -0700110 if (mActionMode == null) {
Jason Chang96f886b2019-03-29 17:59:02 +0800111 if (DEBUG) {
112 Log.w(TAG, "Received call to destroy action mode on alien mode object.");
113 }
Steve McKay4f78ba62016-10-04 16:48:49 -0700114 }
115
116 assert(mActionMode.equals(mode));
117
Jason Chang96f886b2019-03-29 17:59:02 +0800118 if (DEBUG) {
119 Log.d(TAG, "Handling action mode destroyed.");
120 }
Garfield Tan84bd0f12016-09-12 14:18:32 -0700121 mActionMode = null;
Steve McKay4f78ba62016-10-04 16:48:49 -0700122 mMenu = null;
123
Garfield Tan84bd0f12016-09-12 14:18:32 -0700124 mSelectionMgr.clearSelection();
Garfield Tan84bd0f12016-09-12 14:18:32 -0700125
Ben Lin859e42f2017-03-31 12:10:16 -0700126 // Reset window title back to activity title, i.e. Root name
127 mActivity.getWindow().setTitle(mActivity.getTitle());
128
Garfield Tan84bd0f12016-09-12 14:18:32 -0700129 // Re-enable TalkBack for the toolbars, as they are no longer covered by action mode.
Steve McKaybd9f05a2016-10-10 10:18:36 -0700130 mScope.accessibilityImportanceSetter.setAccessibilityImportance(
Garfield Tan84bd0f12016-09-12 14:18:32 -0700131 View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, R.id.toolbar, R.id.roots_toolbar);
132 }
133
134 @Override
135 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
136 int size = mSelectionMgr.getSelection().size();
Ben Linff7f3ae2017-04-25 16:08:52 -0700137 mode.getMenuInflater().inflate(R.menu.action_mode_menu, menu);
Tony Huang057e9872018-11-05 11:23:00 +0800138 mode.setTitle(mActivity.getResources().getQuantityString(R.plurals.selected_count, size));
Garfield Tan84bd0f12016-09-12 14:18:32 -0700139
140 if (size > 0) {
141
142 // Hide the toolbars if action mode is enabled, so TalkBack doesn't navigate to
143 // these controls when using linear navigation.
Steve McKaybd9f05a2016-10-10 10:18:36 -0700144 mScope.accessibilityImportanceSetter.setAccessibilityImportance(
Garfield Tan84bd0f12016-09-12 14:18:32 -0700145 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
146 R.id.toolbar,
147 R.id.roots_toolbar);
148 return true;
149 }
150
151 return false;
152 }
153
154 @Override
155 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
156 mMenu = menu;
157 updateActionMenu();
158 return true;
159 }
160
161 private void updateActionMenu() {
162 assert(mMenu != null);
Steve McKaybd9f05a2016-10-10 10:18:36 -0700163 mMenuManager.updateActionMenu(mMenu, mScope.selectionDetails);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700164 Menus.disableHiddenItems(mMenu);
165 }
166
167 @Override
168 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Steve McKaybd9f05a2016-10-10 10:18:36 -0700169 return mScope.menuItemClicker.accept(item);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700170 }
171
172 private static void setImportantForAccessibility(
173 Activity activity, int accessibilityImportance, @IdRes int[] viewIds) {
174 for (final int id : viewIds) {
175 final View v = activity.findViewById(id);
176 if (v != null) {
177 v.setImportantForAccessibility(accessibilityImportance);
178 }
179 }
180 }
181
182 @FunctionalInterface
183 private interface AccessibilityImportanceSetter {
184 void setAccessibilityImportance(int accessibilityImportance, @IdRes int... viewIds);
185 }
Steve McKayc8889af2016-09-23 11:22:41 -0700186
Steve McKaybd9f05a2016-10-10 10:18:36 -0700187 @Override
188 public void finishActionMode() {
189 if (mActionMode != null) {
190 mActionMode.finish();
191 mActionMode = null;
192 } else {
193 Log.w(TAG, "Tried to finish a null action mode.");
194 }
195 }
196
shawnlin48200cd2018-10-16 16:20:42 +0800197 @Override
198 public void finishOnConfirmed(@Result int code) {
199 if (code == ConfirmationCallback.CONFIRM) {
200 finishActionMode();
201 }
202 }
203
Steve McKay5b0a2c12016-10-07 11:22:31 -0700204 public ActionModeController reset(
Ben Lin11a2c132017-05-03 18:46:37 -0700205 SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker) {
Steve McKay5b0a2c12016-10-07 11:22:31 -0700206 assert(mActionMode == null);
207 assert(mMenu == null);
208
Steve McKaybd9f05a2016-10-10 10:18:36 -0700209 mScope.menuItemClicker = menuItemClicker;
210 mScope.selectionDetails = selectionDetails;
Steve McKaybd9f05a2016-10-10 10:18:36 -0700211 mScope.accessibilityImportanceSetter =
Steve McKay5b0a2c12016-10-07 11:22:31 -0700212 (int accessibilityImportance, @IdRes int[] viewIds) -> {
213 setImportantForAccessibility(
214 mActivity, accessibilityImportance, viewIds);
215 };
216
217 return this;
218 }
219
Steve McKaybd9f05a2016-10-10 10:18:36 -0700220 private static final class ContentScope {
Steve McKay5b0a2c12016-10-07 11:22:31 -0700221 private EventHandler<MenuItem> menuItemClicker;
222 private SelectionDetails selectionDetails;
Steve McKay5b0a2c12016-10-07 11:22:31 -0700223 private AccessibilityImportanceSetter accessibilityImportanceSetter;
224 }
Garfield Tan84bd0f12016-09-12 14:18:32 -0700225}