Home | History | Annotate | Download | only in documentsui
      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 
     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
     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
    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
    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
    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(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
    171     public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
    172         mMenu = menu;
    173         updateActionMenu();
    174         return true;
    175     }
    176 
    177     private void updateActionMenu() {
    178         assert(mMenu != null);
    179         mMenuManager.updateActionMenu(mMenu, mScope.selectionDetails);
    180         Menus.disableHiddenItems(mMenu);
    181     }
    182 
    183     @Override
    184     public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
    185         return mScope.menuItemClicker.accept(item);
    186     }
    187 
    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 {
    200         void setAccessibilityImportance(int accessibilityImportance, @IdRes int... viewIds);
    201     }
    202 
    203     @Override
    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
    214     public void finishOnConfirmed(@Result int code) {
    215         if (code == ConfirmationCallback.CONFIRM) {
    216             finishActionMode();
    217         }
    218     }
    219 
    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