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