blob: 016d9370364d37ec7cd663de5bacd1d6a8e0be9a [file] [log] [blame]
/*
* Copyright (C) 2014 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.systemui.recents.views;
import android.content.res.Configuration;
import android.graphics.Point;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartInitializeDropTargetsEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import java.util.ArrayList;
/**
* Represents the dock regions for each orientation.
*/
class DockRegion {
public static TaskStack.DockState[] PHONE_LANDSCAPE = {
// We only allow docking to the left for now on small devices
TaskStack.DockState.LEFT
};
public static TaskStack.DockState[] PHONE_PORTRAIT = {
// We only allow docking to the top for now on small devices
TaskStack.DockState.TOP
};
public static TaskStack.DockState[] TABLET_LANDSCAPE = {
TaskStack.DockState.LEFT,
TaskStack.DockState.RIGHT
};
public static TaskStack.DockState[] TABLET_PORTRAIT = PHONE_PORTRAIT;
}
/**
* Handles touch events for a RecentsView.
*/
public class RecentsViewTouchHandler {
private RecentsView mRv;
@ViewDebug.ExportedProperty(deepExport=true, prefix="drag_task")
private Task mDragTask;
@ViewDebug.ExportedProperty(deepExport=true, prefix="drag_task_view_")
private TaskView mTaskView;
@ViewDebug.ExportedProperty(category="recents")
private Point mTaskViewOffset = new Point();
@ViewDebug.ExportedProperty(category="recents")
private Point mDownPos = new Point();
@ViewDebug.ExportedProperty(category="recents")
private boolean mDragRequested;
@ViewDebug.ExportedProperty(category="recents")
private boolean mIsDragging;
private float mDragSlop;
private DropTarget mLastDropTarget;
private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
private ArrayList<TaskStack.DockState> mVisibleDockStates = new ArrayList<>();
public RecentsViewTouchHandler(RecentsView rv) {
mRv = rv;
mDragSlop = ViewConfiguration.get(rv.getContext()).getScaledTouchSlop();
}
/**
* Registers a new drop target for the current drag only.
*/
public void registerDropTargetForCurrentDrag(DropTarget target) {
mDropTargets.add(target);
}
/**
* Returns the preferred dock states for the current orientation.
*/
public TaskStack.DockState[] getDockStatesForCurrentOrientation() {
boolean isLandscape = mRv.getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
RecentsConfiguration config = Recents.getConfiguration();
TaskStack.DockState[] dockStates = isLandscape ?
(config.isLargeScreen ? DockRegion.TABLET_LANDSCAPE : DockRegion.PHONE_LANDSCAPE) :
(config.isLargeScreen ? DockRegion.TABLET_PORTRAIT : DockRegion.PHONE_PORTRAIT);
return dockStates;
}
/**
* Returns the set of visible dock states for this current drag.
*/
public ArrayList<TaskStack.DockState> getVisibleDockStates() {
return mVisibleDockStates;
}
/** Touch preprocessing for handling below */
public boolean onInterceptTouchEvent(MotionEvent ev) {
handleTouchEvent(ev);
return mDragRequested;
}
/** Handles touch events once we have intercepted them */
public boolean onTouchEvent(MotionEvent ev) {
handleTouchEvent(ev);
return mDragRequested;
}
/**** Events ****/
public final void onBusEvent(DragStartEvent event) {
SystemServicesProxy ssp = Recents.getSystemServices();
mRv.getParent().requestDisallowInterceptTouchEvent(true);
mDragRequested = true;
// We defer starting the actual drag handling until the user moves past the drag slop
mIsDragging = false;
mDragTask = event.task;
mTaskView = event.taskView;
mDropTargets.clear();
int[] recentsViewLocation = new int[2];
mRv.getLocationInWindow(recentsViewLocation);
mTaskViewOffset.set(mTaskView.getLeft() - recentsViewLocation[0] + event.tlOffset.x,
mTaskView.getTop() - recentsViewLocation[1] + event.tlOffset.y);
float x = mDownPos.x - mTaskViewOffset.x;
float y = mDownPos.y - mTaskViewOffset.y;
mTaskView.setTranslationX(x);
mTaskView.setTranslationY(y);
mVisibleDockStates.clear();
if (!ssp.hasDockedTask() && mRv.getTaskStack().getTaskCount() > 1) {
// Add the dock state drop targets (these take priority)
TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation();
for (TaskStack.DockState dockState : dockStates) {
registerDropTargetForCurrentDrag(dockState);
mVisibleDockStates.add(dockState);
}
}
// Request other drop targets to register themselves
EventBus.getDefault().send(new DragStartInitializeDropTargetsEvent(event.task,
event.taskView, this));
}
public final void onBusEvent(DragEndEvent event) {
mDragRequested = false;
mDragTask = null;
mTaskView = null;
mLastDropTarget = null;
}
/**
* Handles dragging touch events
*/
private void handleTouchEvent(MotionEvent ev) {
int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
mDownPos.set((int) ev.getX(), (int) ev.getY());
break;
case MotionEvent.ACTION_MOVE: {
float evX = ev.getX();
float evY = ev.getY();
float x = evX - mTaskViewOffset.x;
float y = evY - mTaskViewOffset.y;
if (mDragRequested) {
if (!mIsDragging) {
mIsDragging = Math.hypot(evX - mDownPos.x, evY - mDownPos.y) > mDragSlop;
}
if (mIsDragging) {
int width = mRv.getMeasuredWidth();
int height = mRv.getMeasuredHeight();
DropTarget currentDropTarget = null;
// Give priority to the current drop target to retain the touch handling
if (mLastDropTarget != null) {
if (mLastDropTarget.acceptsDrop((int) evX, (int) evY, width, height,
true /* isCurrentTarget */)) {
currentDropTarget = mLastDropTarget;
}
}
// Otherwise, find the next target to handle this event
if (currentDropTarget == null) {
for (DropTarget target : mDropTargets) {
if (target.acceptsDrop((int) evX, (int) evY, width, height,
false /* isCurrentTarget */)) {
currentDropTarget = target;
break;
}
}
}
if (mLastDropTarget != currentDropTarget) {
mLastDropTarget = currentDropTarget;
EventBus.getDefault().send(new DragDropTargetChangedEvent(mDragTask,
currentDropTarget));
}
}
mTaskView.setTranslationX(x);
mTaskView.setTranslationY(y);
}
break;
}
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL: {
if (mDragRequested) {
EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView,
mLastDropTarget));
break;
}
}
}
}
}