blob: 1484c162d2cf539bda60c96ba721a9e822e549ca [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
Garfield Tan84bd0f12016-09-12 14:18:32 -070021import android.app.Activity;
Garfield Tan84bd0f12016-09-12 14:18:32 -070022import android.util.Log;
23import android.view.ActionMode;
Garfield Tan84bd0f12016-09-12 14:18:32 -070024import android.view.Menu;
25import android.view.MenuItem;
26import android.view.View;
27
shawnlin8dafe612019-08-14 20:10:18 +080028import androidx.annotation.IdRes;
29import androidx.annotation.Nullable;
Riddle Hsu0c375982018-06-21 22:06:43 +080030import androidx.recyclerview.selection.MutableSelection;
31import androidx.recyclerview.selection.SelectionTracker;
32import androidx.recyclerview.selection.SelectionTracker.SelectionObserver;
33
shawnlin8dafe612019-08-14 20:10:18 +080034import com.android.documentsui.MenuManager.SelectionDetails;
35import com.android.documentsui.base.EventHandler;
36import com.android.documentsui.base.Menus;
37import com.android.documentsui.ui.MessageBuilder;
38
Garfield Tan84bd0f12016-09-12 14:18:32 -070039/**
40 * A controller that listens to selection changes and manages life cycles of action modes.
41 */
Riddle Hsu0c375982018-06-21 22:06:43 +080042public class ActionModeController extends SelectionObserver<String>
Steve McKay365e3cb2017-08-31 10:27:08 -070043 implements ActionMode.Callback, ActionModeAddons {
Garfield Tan84bd0f12016-09-12 14:18:32 -070044
45 private static final String TAG = "ActionModeController";
46
Steve McKay5b0a2c12016-10-07 11:22:31 -070047 private final Activity mActivity;
Riddle Hsu0c375982018-06-21 22:06:43 +080048 private final SelectionTracker<String> mSelectionMgr;
Garfield Tan84bd0f12016-09-12 14:18:32 -070049 private final MenuManager mMenuManager;
Steve McKay5b0a2c12016-10-07 11:22:31 -070050 private final MessageBuilder mMessages;
Garfield Tan84bd0f12016-09-12 14:18:32 -070051
Steve McKaybd9f05a2016-10-10 10:18:36 -070052 private final ContentScope mScope = new ContentScope();
Riddle Hsu0c375982018-06-21 22:06:43 +080053 private final MutableSelection<String> mSelected = new MutableSelection<>();
Garfield Tan84bd0f12016-09-12 14:18:32 -070054
55 private @Nullable ActionMode mActionMode;
56 private @Nullable Menu mMenu;
57
Steve McKay5b0a2c12016-10-07 11:22:31 -070058 public ActionModeController(
59 Activity activity,
Riddle Hsu0c375982018-06-21 22:06:43 +080060 SelectionTracker<String> selectionMgr,
Garfield Tan84bd0f12016-09-12 14:18:32 -070061 MenuManager menuManager,
Steve McKay5b0a2c12016-10-07 11:22:31 -070062 MessageBuilder messages) {
63
64 mActivity = activity;
Garfield Tan84bd0f12016-09-12 14:18:32 -070065 mSelectionMgr = selectionMgr;
66 mMenuManager = menuManager;
Steve McKay5b0a2c12016-10-07 11:22:31 -070067 mMessages = messages;
Garfield Tan84bd0f12016-09-12 14:18:32 -070068 }
69
70 @Override
71 public void onSelectionChanged() {
Steve McKay2d19a692017-08-22 09:05:08 -070072 mSelectionMgr.copySelection(mSelected);
Garfield Tan84bd0f12016-09-12 14:18:32 -070073 if (mSelected.size() > 0) {
74 if (mActionMode == null) {
Jason Chang96f886b2019-03-29 17:59:02 +080075 if (DEBUG) {
76 Log.d(TAG, "Starting action mode.");
77 }
Steve McKay5b0a2c12016-10-07 11:22:31 -070078 mActionMode = mActivity.startActionMode(this);
Tony Huangddf0b512019-09-05 16:23:49 +080079 final View closeButton = mActivity.findViewById(R.id.action_mode_close_button);
80 if (closeButton != null) {
81 closeButton.setContentDescription(mActivity.getString(android.R.string.cancel));
82 }
Garfield Tan84bd0f12016-09-12 14:18:32 -070083 }
84 updateActionMenu();
85 } else {
86 if (mActionMode != null) {
Jason Chang96f886b2019-03-29 17:59:02 +080087 if (DEBUG) {
88 Log.d(TAG, "Finishing action mode.");
89 }
Garfield Tan84bd0f12016-09-12 14:18:32 -070090 mActionMode.finish();
91 }
92 }
93
94 if (mActionMode != null) {
95 assert(!mSelected.isEmpty());
Steve McKay5b0a2c12016-10-07 11:22:31 -070096 final String title = mMessages.getQuantityString(
Garfield Tan84bd0f12016-09-12 14:18:32 -070097 R.plurals.elements_selected, mSelected.size());
98 mActionMode.setTitle(title);
Ben Linc8739d92016-11-02 11:54:53 -070099 mActivity.getWindow().setTitle(title);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700100 }
101 }
102
103 @Override
104 public void onSelectionRestored() {
Steve McKay365e3cb2017-08-31 10:27:08 -0700105 onSelectionChanged();
Steve McKaye77c0372017-08-18 14:29:40 -0700106 }
107
Garfield Tan84bd0f12016-09-12 14:18:32 -0700108 // Called when the user exits the action mode
109 @Override
110 public void onDestroyActionMode(ActionMode mode) {
Steve McKay4f78ba62016-10-04 16:48:49 -0700111 if (mActionMode == null) {
Jason Chang96f886b2019-03-29 17:59:02 +0800112 if (DEBUG) {
113 Log.w(TAG, "Received call to destroy action mode on alien mode object.");
114 }
Steve McKay4f78ba62016-10-04 16:48:49 -0700115 }
116
117 assert(mActionMode.equals(mode));
118
Jason Chang96f886b2019-03-29 17:59:02 +0800119 if (DEBUG) {
120 Log.d(TAG, "Handling action mode destroyed.");
121 }
Garfield Tan84bd0f12016-09-12 14:18:32 -0700122 mActionMode = null;
Steve McKay4f78ba62016-10-04 16:48:49 -0700123 mMenu = null;
124
Garfield Tan84bd0f12016-09-12 14:18:32 -0700125 mSelectionMgr.clearSelection();
Garfield Tan84bd0f12016-09-12 14:18:32 -0700126
Ben Lin859e42f2017-03-31 12:10:16 -0700127 // Reset window title back to activity title, i.e. Root name
128 mActivity.getWindow().setTitle(mActivity.getTitle());
129
Garfield Tan84bd0f12016-09-12 14:18:32 -0700130 // Re-enable TalkBack for the toolbars, as they are no longer covered by action mode.
Steve McKaybd9f05a2016-10-10 10:18:36 -0700131 mScope.accessibilityImportanceSetter.setAccessibilityImportance(
Garfield Tan84bd0f12016-09-12 14:18:32 -0700132 View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, R.id.toolbar, R.id.roots_toolbar);
133 }
134
135 @Override
136 public boolean onCreateActionMode(ActionMode mode, Menu menu) {
137 int size = mSelectionMgr.getSelection().size();
Ben Linff7f3ae2017-04-25 16:08:52 -0700138 mode.getMenuInflater().inflate(R.menu.action_mode_menu, menu);
Tony Huang057e9872018-11-05 11:23:00 +0800139 mode.setTitle(mActivity.getResources().getQuantityString(R.plurals.selected_count, size));
Garfield Tan84bd0f12016-09-12 14:18:32 -0700140
141 if (size > 0) {
142
143 // Hide the toolbars if action mode is enabled, so TalkBack doesn't navigate to
144 // these controls when using linear navigation.
Steve McKaybd9f05a2016-10-10 10:18:36 -0700145 mScope.accessibilityImportanceSetter.setAccessibilityImportance(
Garfield Tan84bd0f12016-09-12 14:18:32 -0700146 View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
147 R.id.toolbar,
148 R.id.roots_toolbar);
149 return true;
150 }
151
152 return false;
153 }
154
155 @Override
156 public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
157 mMenu = menu;
158 updateActionMenu();
159 return true;
160 }
161
162 private void updateActionMenu() {
163 assert(mMenu != null);
Steve McKaybd9f05a2016-10-10 10:18:36 -0700164 mMenuManager.updateActionMenu(mMenu, mScope.selectionDetails);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700165 Menus.disableHiddenItems(mMenu);
166 }
167
168 @Override
169 public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
Steve McKaybd9f05a2016-10-10 10:18:36 -0700170 return mScope.menuItemClicker.accept(item);
Garfield Tan84bd0f12016-09-12 14:18:32 -0700171 }
172
173 private static void setImportantForAccessibility(
174 Activity activity, int accessibilityImportance, @IdRes int[] viewIds) {
175 for (final int id : viewIds) {
176 final View v = activity.findViewById(id);
177 if (v != null) {
178 v.setImportantForAccessibility(accessibilityImportance);
179 }
180 }
181 }
182
183 @FunctionalInterface
184 private interface AccessibilityImportanceSetter {
185 void setAccessibilityImportance(int accessibilityImportance, @IdRes int... viewIds);
186 }
Steve McKayc8889af2016-09-23 11:22:41 -0700187
Steve McKaybd9f05a2016-10-10 10:18:36 -0700188 @Override
189 public void finishActionMode() {
190 if (mActionMode != null) {
191 mActionMode.finish();
192 mActionMode = null;
193 } else {
194 Log.w(TAG, "Tried to finish a null action mode.");
195 }
196 }
197
Steve McKay5b0a2c12016-10-07 11:22:31 -0700198 public ActionModeController reset(
Ben Lin11a2c132017-05-03 18:46:37 -0700199 SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker) {
Steve McKay5b0a2c12016-10-07 11:22:31 -0700200 assert(mActionMode == null);
201 assert(mMenu == null);
202
Steve McKaybd9f05a2016-10-10 10:18:36 -0700203 mScope.menuItemClicker = menuItemClicker;
204 mScope.selectionDetails = selectionDetails;
Steve McKaybd9f05a2016-10-10 10:18:36 -0700205 mScope.accessibilityImportanceSetter =
Steve McKay5b0a2c12016-10-07 11:22:31 -0700206 (int accessibilityImportance, @IdRes int[] viewIds) -> {
207 setImportantForAccessibility(
208 mActivity, accessibilityImportance, viewIds);
209 };
210
211 return this;
212 }
213
Steve McKaybd9f05a2016-10-10 10:18:36 -0700214 private static final class ContentScope {
Steve McKay5b0a2c12016-10-07 11:22:31 -0700215 private EventHandler<MenuItem> menuItemClicker;
216 private SelectionDetails selectionDetails;
Steve McKay5b0a2c12016-10-07 11:22:31 -0700217 private AccessibilityImportanceSetter accessibilityImportanceSetter;
218 }
Garfield Tan84bd0f12016-09-12 14:18:32 -0700219}