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