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