blob: 0adddcca8ee6f20e07389c1bad926fac1eab9ab5 [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.dirlist;
import static com.android.documentsui.base.SharedMinimal.DEBUG;
import android.net.Uri;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.selection.MutableSelection;
import androidx.recyclerview.selection.Selection;
import androidx.recyclerview.selection.SelectionTracker;
import com.android.documentsui.DragAndDropManager;
import com.android.documentsui.MenuManager.SelectionDetails;
import com.android.documentsui.Model;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.Events;
import com.android.documentsui.base.State;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import javax.annotation.Nullable;
/**
* Listens for potential "drag-like" events and kick-start dragging as needed. Also allows external
* direct call to {@code #startDrag(RecyclerView, View)} if explicit start is needed, such as long-
* pressing on an item via touch. (e.g. InputEventDispatcher#onLongPress(MotionEvent)} via touch.
*/
interface DragStartListener {
DragStartListener STUB = new DragStartListener() {
@Override
public boolean onDragEvent(MotionEvent event) {
return false;
}
};
boolean onDragEvent(MotionEvent event);
@VisibleForTesting
class RuntimeDragStartListener implements DragStartListener {
private static String TAG = "DragStartListener";
private final IconHelper mIconHelper;
private final State mState;
private final SelectionTracker<String> mSelectionMgr;
private final SelectionDetails mSelectionDetails;
private final ViewFinder mViewFinder;
private final Function<View, String> mIdFinder;
private final Function<Selection<String>, List<DocumentInfo>> mDocsConverter;
private final DragAndDropManager mDragAndDropManager;
// use DragStartListener.create
@VisibleForTesting
public RuntimeDragStartListener(
IconHelper iconHelper,
State state,
SelectionTracker<String> selectionMgr,
SelectionDetails selectionDetails,
ViewFinder viewFinder,
Function<View, String> idFinder,
Function<Selection<String>, List<DocumentInfo>> docsConverter,
DragAndDropManager dragAndDropManager) {
mIconHelper = iconHelper;
mState = state;
mSelectionMgr = selectionMgr;
mSelectionDetails = selectionDetails;
mViewFinder = viewFinder;
mIdFinder = idFinder;
mDocsConverter = docsConverter;
mDragAndDropManager = dragAndDropManager;
}
@Override
public final boolean onDragEvent(MotionEvent event) {
return startDrag(mViewFinder.findView(event.getX(), event.getY()), event);
}
/**
* May be called externally when drag is initiated from other event handling code.
*/
private boolean startDrag(@Nullable View view, MotionEvent event) {
if (view == null) {
if (DEBUG) {
Log.d(TAG, "Ignoring drag event, null view.");
}
return false;
}
@Nullable String modelId = mIdFinder.apply(view);
if (modelId == null) {
if (DEBUG) {
Log.d(TAG, "Ignoring drag on view not represented in model.");
}
return false;
}
Selection<String> selection = getSelectionToBeCopied(modelId, event);
final List<DocumentInfo> srcs = mDocsConverter.apply(selection);
final List<Uri> invalidDest = new ArrayList<>(srcs.size() + 1);
for (DocumentInfo doc : srcs) {
invalidDest.add(doc.derivedUri);
}
final DocumentInfo parent = mState.stack.peek();
// parent is null when we're in Recents
if (parent != null) {
invalidDest.add(parent.derivedUri);
}
mDragAndDropManager.startDrag(view, srcs, mState.stack.getRoot(), invalidDest,
mSelectionDetails, mIconHelper, parent);
return true;
}
/**
* Given the MotionEvent (for CTRL case) and modelId of the view associated with the
* coordinates of the event, return a valid selection for drag and drop operation
*/
@VisibleForTesting
MutableSelection<String> getSelectionToBeCopied(String modelId, MotionEvent event) {
MutableSelection<String> selection = new MutableSelection<>();
// If CTRL-key is held down and there's other existing selection, add item to
// selection (if not already selected)
if (Events.isCtrlKeyPressed(event)
&& mSelectionMgr.hasSelection()
&& !mSelectionMgr.isSelected(modelId)) {
mSelectionMgr.select(modelId);
}
if (mSelectionMgr.isSelected(modelId)) {
mSelectionMgr.copySelection(selection);
} else {
selection.add(modelId);
mSelectionMgr.clearSelection();
}
return selection;
}
}
static DragStartListener create(
IconHelper iconHelper,
Model model,
SelectionTracker<String> selectionMgr,
SelectionDetails selectionDetails,
State state,
Function<View, String> idFinder,
ViewFinder viewFinder,
DragAndDropManager dragAndDropManager) {
return new RuntimeDragStartListener(
iconHelper,
state,
selectionMgr,
selectionDetails,
viewFinder,
idFinder,
model::getDocuments,
dragAndDropManager);
}
@FunctionalInterface
interface ViewFinder {
@Nullable View findView(float x, float y);
}
}