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