blob: 89b8ff3834dcd75fe1509beb63904c6a0987132c [file] [log] [blame]
/*
* Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.documentsui;
import static com.android.documentsui.base.SharedMinimal.DEBUG;
import android.app.Activity;
import android.util.Log;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import androidx.annotation.IdRes;
import androidx.annotation.Nullable;
import androidx.recyclerview.selection.MutableSelection;
import androidx.recyclerview.selection.SelectionTracker;
import androidx.recyclerview.selection.SelectionTracker.SelectionObserver;
import com.android.documentsui.MenuManager.SelectionDetails;
import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.Menus;
import com.android.documentsui.ui.MessageBuilder;
/**
* A controller that listens to selection changes and manages life cycles of action modes.
*/
public class ActionModeController extends SelectionObserver<String>
implements ActionMode.Callback, ActionModeAddons {
private static final String TAG = "ActionModeController";
private final Activity mActivity;
private final SelectionTracker<String> mSelectionMgr;
private final NavigationViewManager mNavigator;
private final MenuManager mMenuManager;
private final MessageBuilder mMessages;
private final ContentScope mScope = new ContentScope();
private final MutableSelection<String> mSelected = new MutableSelection<>();
private @Nullable ActionMode mActionMode;
private @Nullable Menu mMenu;
public ActionModeController(
Activity activity,
SelectionTracker<String> selectionMgr,
NavigationViewManager navigator,
MenuManager menuManager,
MessageBuilder messages) {
mActivity = activity;
mSelectionMgr = selectionMgr;
mNavigator = navigator;
mMenuManager = menuManager;
mMessages = messages;
}
@Override
public void onSelectionChanged() {
mSelectionMgr.copySelection(mSelected);
if (mSelected.size() > 0) {
if (mActionMode == null) {
if (DEBUG) {
Log.d(TAG, "Starting action mode.");
}
mActionMode = mActivity.startActionMode(this);
final View closeButton = mActivity.findViewById(R.id.action_mode_close_button);
if (closeButton != null) {
closeButton.setContentDescription(mActivity.getString(android.R.string.cancel));
}
}
updateActionMenu();
} else {
if (mActionMode != null) {
if (DEBUG) {
Log.d(TAG, "Finishing action mode.");
}
mActionMode.finish();
}
}
if (mActionMode != null) {
assert(!mSelected.isEmpty());
final String title = mMessages.getQuantityString(
R.plurals.elements_selected, mSelected.size());
mActionMode.setTitle(title);
mActivity.getWindow().setTitle(title);
}
}
@Override
public void onSelectionRestored() {
onSelectionChanged();
}
// Called when the user exits the action mode
@Override
public void onDestroyActionMode(ActionMode mode) {
if (mActionMode == null) {
if (DEBUG) {
Log.w(TAG, "Received call to destroy action mode on alien mode object.");
}
}
assert(mActionMode.equals(mode));
if (DEBUG) {
Log.d(TAG, "Handling action mode destroyed.");
}
mActionMode = null;
mMenu = null;
if (mSelected.size() > 0) {
mSelectionMgr.clearSelection();
}
// Reset window title back to activity title, i.e. Root name
mActivity.getWindow().setTitle(mActivity.getTitle());
// Re-enable TalkBack for the toolbars, as they are no longer covered by action mode.
mScope.accessibilityImportanceSetter.setAccessibilityImportance(
View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, R.id.toolbar, R.id.roots_toolbar);
mNavigator.setActionModeActivated(false);
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
int size = mSelectionMgr.getSelection().size();
mode.getMenuInflater().inflate(R.menu.action_mode_menu, menu);
mode.setTitle(mActivity.getResources().getQuantityString(R.plurals.selected_count, size));
if (size > 0) {
mNavigator.setActionModeActivated(true);
// Hide the toolbars if action mode is enabled, so TalkBack doesn't navigate to
// these controls when using linear navigation.
mScope.accessibilityImportanceSetter.setAccessibilityImportance(
View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
R.id.toolbar,
R.id.roots_toolbar);
return true;
}
return false;
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
mMenu = menu;
updateActionMenu();
return true;
}
private void updateActionMenu() {
assert(mMenu != null);
mMenuManager.updateActionMenu(mMenu, mScope.selectionDetails);
Menus.disableHiddenItems(mMenu);
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return mScope.menuItemClicker.accept(item);
}
private static void setImportantForAccessibility(
Activity activity, int accessibilityImportance, @IdRes int[] viewIds) {
for (final int id : viewIds) {
final View v = activity.findViewById(id);
if (v != null) {
v.setImportantForAccessibility(accessibilityImportance);
}
}
}
@FunctionalInterface
private interface AccessibilityImportanceSetter {
void setAccessibilityImportance(int accessibilityImportance, @IdRes int... viewIds);
}
@Override
public void finishActionMode() {
if (mActionMode != null) {
mActionMode.finish();
mActionMode = null;
} else {
Log.w(TAG, "Tried to finish a null action mode.");
}
}
public ActionModeController reset(
SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker) {
assert(mActionMode == null);
assert(mMenu == null);
mScope.menuItemClicker = menuItemClicker;
mScope.selectionDetails = selectionDetails;
mScope.accessibilityImportanceSetter =
(int accessibilityImportance, @IdRes int[] viewIds) -> {
setImportantForAccessibility(
mActivity, accessibilityImportance, viewIds);
};
return this;
}
private static final class ContentScope {
private EventHandler<MenuItem> menuItemClicker;
private SelectionDetails selectionDetails;
private AccessibilityImportanceSetter accessibilityImportanceSetter;
}
}