blob: 636d4d6243c6e6e60934b6ac7818af2d65fd25d8 [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.app.ActivityManager;
import android.content.res.Configuration;
import android.graphics.Point;
import android.graphics.Rect;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.ViewDebug;
import com.android.internal.policy.DividerSnapAlgorithm;
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.activity.ConfigurationChangedEvent;
import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent;
import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
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 in landscape 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 DividerSnapAlgorithm mDividerSnapAlgorithm;
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();
updateSnapAlgorithm();
}
private void updateSnapAlgorithm() {
Rect insets = new Rect();
SystemServicesProxy.getInstance(mRv.getContext()).getStableInsets(insets);
mDividerSnapAlgorithm = DividerSnapAlgorithm.create(mRv.getContext(), insets);
}
/**
* 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();
if (config.isLargeScreen) {
return isLandscape ? DockRegion.TABLET_LANDSCAPE : DockRegion.TABLET_PORTRAIT;
} else {
return isLandscape ? DockRegion.PHONE_LANDSCAPE : DockRegion.PHONE_PORTRAIT;
}
}
/**
* 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 (ActivityManager.supportsMultiWindow() && !ssp.hasDockedTask()
&& mDividerSnapAlgorithm.isSplitScreenFeasible()) {
Recents.logDockAttempt(mRv.getContext(), event.task.getTopComponent(),
event.task.resizeMode);
if (!event.task.isDockable) {
EventBus.getDefault().send(new ShowIncompatibleAppOverlayEvent());
} else {
// Add the dock state drop targets (these take priority)
TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation();
for (TaskStack.DockState dockState : dockStates) {
registerDropTargetForCurrentDrag(dockState);
dockState.update(mRv.getContext());
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) {
if (!mDragTask.isDockable) {
EventBus.getDefault().send(new HideIncompatibleAppOverlayEvent());
}
mDragRequested = false;
mDragTask = null;
mTaskView = null;
mLastDropTarget = null;
}
public final void onBusEvent(ConfigurationChangedEvent event) {
if (event.fromDisplayDensityChange || event.fromDeviceOrientationChange) {
updateSnapAlgorithm();
}
}
/**
* Handles dragging touch events
*/
private void handleTouchEvent(MotionEvent ev) {
int action = ev.getActionMasked();
switch (action) {
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,
mRv.mSystemInsets, 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,
mRv.mSystemInsets, 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) {
boolean cancelled = action == MotionEvent.ACTION_CANCEL;
if (cancelled) {
EventBus.getDefault().send(new DragDropTargetChangedEvent(mDragTask, null));
}
EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView,
!cancelled ? mLastDropTarget : null));
break;
}
}
}
}
}