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