• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.documentsui;
18 
19 import static com.android.documentsui.base.SharedMinimal.DEBUG;
20 import static com.android.documentsui.util.FlagUtils.isUseMaterial3FlagEnabled;
21 
22 import android.app.Activity;
23 import android.util.Log;
24 import android.view.ActionMode;
25 import android.view.Menu;
26 import android.view.MenuItem;
27 import android.view.View;
28 
29 import androidx.annotation.IdRes;
30 import androidx.annotation.Nullable;
31 import androidx.recyclerview.selection.MutableSelection;
32 import androidx.recyclerview.selection.SelectionTracker;
33 import androidx.recyclerview.selection.SelectionTracker.SelectionObserver;
34 
35 import com.android.documentsui.MenuManager.SelectionDetails;
36 import com.android.documentsui.base.EventHandler;
37 import com.android.documentsui.base.Menus;
38 import com.android.documentsui.ui.MessageBuilder;
39 
40 /**
41  * A controller that listens to selection changes and manages life cycles of action modes.
42  * TODO(b/379776735): This class (and action mode in general) is no longer in use when the
43  * use_material3 flag is enabled. Remove the class once the flag is rolled out.
44  */
45 public class ActionModeController extends SelectionObserver<String>
46         implements ActionMode.Callback, ActionModeAddons {
47 
48     private static final String TAG = "ActionModeController";
49 
50     private final Activity mActivity;
51     private final SelectionTracker<String> mSelectionMgr;
52     private final NavigationViewManager mNavigator;
53     private final MenuManager mMenuManager;
54     private final MessageBuilder mMessages;
55 
56     private final ContentScope mScope = new ContentScope();
57     private final MutableSelection<String> mSelected = new MutableSelection<>();
58 
59     private @Nullable ActionMode mActionMode;
60     private @Nullable Menu mMenu;
61 
ActionModeController( Activity activity, SelectionTracker<String> selectionMgr, NavigationViewManager navigator, MenuManager menuManager, MessageBuilder messages)62     public ActionModeController(
63             Activity activity,
64             SelectionTracker<String> selectionMgr,
65             NavigationViewManager navigator,
66             MenuManager menuManager,
67             MessageBuilder messages) {
68 
69         mActivity = activity;
70         mSelectionMgr = selectionMgr;
71         mNavigator = navigator;
72         mMenuManager = menuManager;
73         mMessages = messages;
74     }
75 
76     @Override
onSelectionChanged()77     public void onSelectionChanged() {
78         mSelectionMgr.copySelection(mSelected);
79         if (mSelected.size() > 0) {
80             if (mActionMode == null) {
81                 if (DEBUG) {
82                     Log.d(TAG, "Starting action mode.");
83                 }
84                 mActionMode = mActivity.startActionMode(this);
85                 final View closeButton =
86                         mActivity.findViewById(androidx.appcompat.R.id.action_mode_close_button);
87                 if (closeButton != null) {
88                     closeButton.setContentDescription(mActivity.getString(android.R.string.cancel));
89                 }
90             }
91             updateActionMenu();
92         } else {
93             if (mActionMode != null) {
94                 if (DEBUG) {
95                     Log.d(TAG, "Finishing action mode.");
96                 }
97                 mActionMode.finish();
98             }
99         }
100 
101         if (mActionMode != null) {
102             assert(!mSelected.isEmpty());
103             final String title = mMessages.getQuantityString(
104                     R.plurals.elements_selected, mSelected.size());
105             mActionMode.setTitle(title);
106             mActivity.getWindow().setTitle(title);
107         }
108     }
109 
110     @Override
onSelectionRestored()111     public void onSelectionRestored() {
112         onSelectionChanged();
113     }
114 
115     // Called when the user exits the action mode
116     @Override
onDestroyActionMode(ActionMode mode)117     public void onDestroyActionMode(ActionMode mode) {
118         if (mActionMode == null) {
119             if (DEBUG) {
120                 Log.w(TAG, "Received call to destroy action mode on alien mode object.");
121             }
122         }
123 
124         assert(mActionMode.equals(mode));
125 
126         if (DEBUG) {
127             Log.d(TAG, "Handling action mode destroyed.");
128         }
129         mActionMode = null;
130         mMenu = null;
131 
132         if (mSelected.size() > 0) {
133             mSelectionMgr.clearSelection();
134         }
135 
136         // Reset window title back to activity title, i.e. Root name
137         mActivity.getWindow().setTitle(mActivity.getTitle());
138 
139         // Re-enable TalkBack for the toolbars, as they are no longer covered by action mode.
140         int[] toolbarIds =
141                 isUseMaterial3FlagEnabled()
142                         ? new int[] {R.id.toolbar}
143                         : new int[] {R.id.toolbar, R.id.roots_toolbar};
144         mScope.accessibilityImportanceSetter.setAccessibilityImportance(
145                 View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, toolbarIds);
146 
147         mNavigator.setActionModeActivated(false);
148     }
149 
150     @Override
onCreateActionMode(ActionMode mode, Menu menu)151     public boolean onCreateActionMode(ActionMode mode, Menu menu) {
152         int size = mSelectionMgr.getSelection().size();
153         mode.getMenuInflater().inflate(R.menu.action_mode_menu, menu);
154         mode.setTitle(mActivity.getResources().getQuantityString(R.plurals.selected_count, size));
155 
156         if (size > 0) {
157             mNavigator.setActionModeActivated(true);
158 
159             // Hide the toolbars if action mode is enabled, so TalkBack doesn't navigate to
160             // these controls when using linear navigation.
161             int[] toolbarIds =
162                     isUseMaterial3FlagEnabled()
163                             ? new int[] {R.id.toolbar}
164                             : new int[] {R.id.toolbar, R.id.roots_toolbar};
165             mScope.accessibilityImportanceSetter.setAccessibilityImportance(
166                     View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
167                     toolbarIds);
168             return true;
169         }
170 
171         return false;
172     }
173 
174     @Override
onPrepareActionMode(ActionMode mode, Menu menu)175     public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
176         mMenu = menu;
177         updateActionMenu();
178         return true;
179     }
180 
updateActionMenu()181     private void updateActionMenu() {
182         assert(mMenu != null);
183         mMenuManager.updateActionMenu(mMenu, mScope.selectionDetails);
184         Menus.disableHiddenItems(mMenu);
185     }
186 
187     @Override
onActionItemClicked(ActionMode mode, MenuItem item)188     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
189         return mScope.menuItemClicker.accept(item);
190     }
191 
setImportantForAccessibility( Activity activity, int accessibilityImportance, @IdRes int[] viewIds)192     private static void setImportantForAccessibility(
193             Activity activity, int accessibilityImportance, @IdRes int[] viewIds) {
194         for (final int id : viewIds) {
195             final View v = activity.findViewById(id);
196             if (v != null) {
197                 v.setImportantForAccessibility(accessibilityImportance);
198             }
199         }
200     }
201 
202     @FunctionalInterface
203     private interface AccessibilityImportanceSetter {
setAccessibilityImportance(int accessibilityImportance, @IdRes int... viewIds)204         void setAccessibilityImportance(int accessibilityImportance, @IdRes int... viewIds);
205     }
206 
207     @Override
finishActionMode()208     public void finishActionMode() {
209         if (mActionMode != null) {
210             mActionMode.finish();
211             mActionMode = null;
212         } else {
213             Log.w(TAG, "Tried to finish a null action mode.");
214         }
215     }
216 
reset( SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker)217     public ActionModeController reset(
218             SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker) {
219         assert(mActionMode == null);
220         assert(mMenu == null);
221 
222         mScope.menuItemClicker = menuItemClicker;
223         mScope.selectionDetails = selectionDetails;
224         mScope.accessibilityImportanceSetter =
225                 (int accessibilityImportance, @IdRes int[] viewIds) -> {
226                     setImportantForAccessibility(
227                             mActivity, accessibilityImportance, viewIds);
228                 };
229 
230         return this;
231     }
232 
233     private static final class ContentScope {
234         private EventHandler<MenuItem> menuItemClicker;
235         private SelectionDetails selectionDetails;
236         private AccessibilityImportanceSetter accessibilityImportanceSetter;
237     }
238 }
239