Merge "Adding dnd to/from freeform workspaces."

This commit is contained in:
Winson Chung
2015-11-05 01:59:57 +00:00
committed by Android (Google) Code Review
16 changed files with 458 additions and 156 deletions

View File

@@ -110,7 +110,8 @@ public class RecentsConfiguration {
void update(Context context, SystemServicesProxy ssp, Rect windowRect) {
// Only update resources that can change after the first load, either through developer
// settings or via multi window
lockToAppEnabled = ssp.getSystemSetting(context, Settings.System.LOCK_TO_APP_ENABLED) != 0;
lockToAppEnabled = !ssp.hasFreeformWorkspaceSupport() &&
ssp.getSystemSetting(context, Settings.System.LOCK_TO_APP_ENABLED) != 0;
hasDockedTasks = ssp.hasDockedTask();
// Recompute some values based on the given state, since we can not rely on the resource

View File

@@ -514,7 +514,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
reloadHeaderBarLayout(false /* tryAndBindSearchWidget */);
// Update the destination rect
mDummyStackView.updateMinMaxScrollForStack(stack);
mDummyStackView.updateLayoutForStack(stack);
final Task toTask = new Task();
final TaskViewTransform toTransform = getThumbnailTransitionTransform(stack, stackView,
topTask.id, toTask);
@@ -696,7 +696,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
TaskStack stack = sInstanceLoadPlan.getTaskStack();
// Prepare the dummy stack for the transition
mDummyStackView.updateMinMaxScrollForStack(stack);
mDummyStackView.updateLayoutForStack(stack);
TaskStackLayoutAlgorithm.VisibilityReport stackVr =
mDummyStackView.computeStackVisibilityReport();
boolean hasRecentTasks = stack.getTaskCount() > 0;

View File

@@ -549,7 +549,7 @@ public class EventBus extends BroadcastReceiver {
InvocationTargetException|
InstantiationException|
IllegalAccessException e) {
Log.e(TAG, "Failed to create InterprocessEvent", e);
Log.e(TAG, "Failed to create InterprocessEvent", e.getCause());
}
}
@@ -746,9 +746,9 @@ public class EventBus extends BroadcastReceiver {
Log.e(TAG, "Failed to deliver event to null subscriber");
}
} catch (IllegalAccessException e) {
Log.e(TAG, "Failed to invoke method", e);
Log.e(TAG, "Failed to invoke method", e.getCause());
} catch (InvocationTargetException e) {
throw new RuntimeException(e.getCause());
Log.e(TAG, "Failed to invoke method", e.getCause());
}
}

View File

@@ -18,18 +18,18 @@ package com.android.systemui.recents.events.ui.dragndrop;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.DropTarget;
/**
* This event is sent when a user drag enters or exits a dock region.
* This event is sent when a user drags in/out of a drop target.
*/
public class DragDockStateChangedEvent extends EventBus.Event {
public class DragDropTargetChangedEvent extends EventBus.Event {
public final Task task;
public final TaskStack.DockState dockState;
public final DropTarget dropTarget;
public DragDockStateChangedEvent(Task task, TaskStack.DockState dockState) {
public DragDropTargetChangedEvent(Task task, DropTarget dropTarget) {
this.task = task;
this.dockState = dockState;
this.dropTarget = dropTarget;
}
}

View File

@@ -19,8 +19,8 @@ package com.android.systemui.recents.events.ui.dragndrop;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.recents.views.DragView;
import com.android.systemui.recents.views.DropTarget;
import com.android.systemui.recents.views.TaskView;
/**
@@ -31,15 +31,15 @@ public class DragEndEvent extends EventBus.Event {
public final Task task;
public final TaskView taskView;
public final DragView dragView;
public final TaskStack.DockState dockState;
public final DropTarget dropTarget;
public final ReferenceCountedTrigger postAnimationTrigger;
public DragEndEvent(Task task, TaskView taskView, DragView dragView,
TaskStack.DockState dockState, ReferenceCountedTrigger postAnimationTrigger) {
public DragEndEvent(Task task, TaskView taskView, DragView dragView, DropTarget dropTarget,
ReferenceCountedTrigger postAnimationTrigger) {
this.task = task;
this.taskView = taskView;
this.dragView = dragView;
this.dockState = dockState;
this.dropTarget = dropTarget;
this.postAnimationTrigger = postAnimationTrigger;
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright (C) 2015 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.events.ui.dragndrop;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.views.RecentsViewTouchHandler;
/**
* This event is sent by the drag manager when it requires drop targets to register themselves for
* the current drag gesture.
*/
public class DragStartInitializeDropTargetsEvent extends EventBus.Event {
public final Task task;
public final RecentsViewTouchHandler handler;
public DragStartInitializeDropTargetsEvent(Task task, RecentsViewTouchHandler handler) {
this.task = task;
this.handler = handler;
}
}

View File

@@ -103,6 +103,7 @@ public class SystemServicesProxy {
Display mDisplay;
String mRecentsPackage;
ComponentName mAssistComponent;
boolean mHasFreeformWorkspaceSupport;
Bitmap mDummyIcon;
int mDummyThumbnailWidth;
@@ -123,6 +124,8 @@ public class SystemServicesProxy {
mUm = UserManager.get(context);
mDisplay = mWm.getDefaultDisplay();
mRecentsPackage = context.getPackageName();
mHasFreeformWorkspaceSupport = false && mPm.hasSystemFeature(
PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
// Get the dummy thumbnail width/heights
Resources res = context.getResources();
@@ -241,7 +244,7 @@ public class SystemServicesProxy {
public boolean hasFreeformWorkspaceSupport() {
if (mPm == null) return false;
return mPm.hasSystemFeature(PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT);
return mHasFreeformWorkspaceSupport;
}
/** Returns whether the recents is currently running */
@@ -411,6 +414,19 @@ public class SystemServicesProxy {
return thumbnail;
}
/**
* Moves a task into another stack.
*/
public void moveTaskToStack(int taskId, int stackId) {
if (mIam == null) return;
try {
mIam.positionTaskInStack(taskId, stackId, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
/** Moves a task to the front with the specified activity options. */
public void moveTaskToFront(int taskId, ActivityOptions opts) {
if (mAm == null) return;

View File

@@ -22,6 +22,8 @@ import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import java.util.Objects;
@@ -165,7 +167,9 @@ public class Task {
this.group = group;
}
/** Updates the stack id of this task. */
/**
* Updates the stack id of this task.
*/
public void setStackId(int stackId) {
key.stackId = stackId;
if (mCb != null) {
@@ -173,10 +177,12 @@ public class Task {
}
}
/**
* Returns whether this task is on the freeform task stack.
*/
public boolean isFreeformTask() {
// Temporarily disable:
return false;
// return SystemServicesProxy.isFreeformStack(key.stackId);
SystemServicesProxy ssp = Recents.getSystemServices();
return ssp.hasFreeformWorkspaceSupport() && ssp.isFreeformStack(key.stackId);
}
/** Notifies the callback listeners that this task has been loaded */
@@ -210,7 +216,7 @@ public class Task {
if (group != null) {
groupAffiliation = Integer.toString(group.affiliation);
}
return "Task (" + groupAffiliation + "): " + key.getComponent().getPackageName() +
return "Task (" + groupAffiliation + "): " + key +
" [" + super.toString() + "]";
}
}

View File

@@ -17,18 +17,21 @@
package com.android.systemui.recents.model;
import android.animation.ObjectAnimator;
import android.app.ActivityManager;
import android.content.ComponentName;
import android.content.Context;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.util.Log;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.misc.NamedCounter;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.views.DropTarget;
import java.util.ArrayList;
import java.util.Collections;
@@ -40,6 +43,8 @@ import java.util.Random;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
/**
@@ -54,9 +59,13 @@ interface TaskFilter {
* A list of filtered tasks.
*/
class FilteredTaskList {
ArrayList<Task> mTasks = new ArrayList<Task>();
ArrayList<Task> mFilteredTasks = new ArrayList<Task>();
HashMap<Task.TaskKey, Integer> mTaskIndices = new HashMap<Task.TaskKey, Integer>();
private static final String TAG = "FilteredTaskList";
private static final boolean DEBUG = true;
ArrayList<Task> mTasks = new ArrayList<>();
ArrayList<Task> mFilteredTasks = new ArrayList<>();
HashMap<Task.TaskKey, Integer> mTaskIndices = new HashMap<>();
TaskFilter mFilter;
/** Sets the task filter, saving the current touch state */
@@ -93,6 +102,25 @@ class FilteredTaskList {
updateFilteredTasks();
}
/**
* Moves the given task.
*/
public void moveTaskToStack(Task task, int insertIndex, int newStackId) {
int taskIndex = indexOf(task);
if (taskIndex != insertIndex) {
mTasks.remove(taskIndex);
if (taskIndex < insertIndex) {
insertIndex--;
}
mTasks.add(insertIndex, task);
}
// Update the stack id now, after we've moved the task, and before we update the
// filtered tasks
task.setStackId(newStackId);
updateFilteredTasks();
}
/** Sets the list of tasks */
void set(List<Task> tasks) {
mTasks.clear();
@@ -187,16 +215,21 @@ public class TaskStack {
}
public enum DockState {
NONE(-1, 96, null, null, null),
public enum DockState implements DropTarget {
NONE(-1, 96, null, null),
LEFT(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, 192,
new RectF(0, 0, 0.3f, 1), new RectF(0, 0, 0.3f, 1), new RectF(0.7f, 0, 1, 1)),
new RectF(0, 0, 0.25f, 1), new RectF(0, 0, 0.25f, 1)),
TOP(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, 192,
new RectF(0, 0, 1, 0.3f), new RectF(0, 0, 1, 0.3f), new RectF(0, 0.7f, 1, 1)),
new RectF(0, 0, 1, 0.25f), new RectF(0, 0, 1, 0.25f)),
RIGHT(DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, 192,
new RectF(0.7f, 0, 1, 1), new RectF(0.7f, 0, 1, 1), new RectF(0, 0, 0.3f, 1)),
new RectF(0.75f, 0, 1, 1), new RectF(0.75f, 0, 1, 1)),
BOTTOM(DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, 192,
new RectF(0, 0.7f, 1, 1), new RectF(0, 0.7f, 1, 1), new RectF(0, 0, 1, 0.3f));
new RectF(0, 0.75f, 1, 1), new RectF(0, 0.75f, 1, 1));
@Override
public boolean acceptsDrop(int x, int y, int width, int height) {
return touchAreaContainsPoint(width, height, x, y);
}
// Represents the view state of this dock state
public class ViewState {
@@ -229,20 +262,17 @@ public class TaskStack {
public final ViewState viewState;
private final RectF dockArea;
private final RectF touchArea;
private final RectF stackArea;
/**
* @param createMode used to pass to ActivityManager to dock the task
* @param touchArea the area in which touch will initiate this dock state
* @param stackArea the area for the stack if a task is docked
* @param dockArea the visible dock area
*/
DockState(int createMode, int dockAreaAlpha, RectF touchArea, RectF dockArea,
RectF stackArea) {
DockState(int createMode, int dockAreaAlpha, RectF touchArea, RectF dockArea) {
this.createMode = createMode;
this.viewState = new ViewState(dockAreaAlpha);
this.dockArea = dockArea;
this.touchArea = touchArea;
this.stackArea = stackArea;
}
/**
@@ -264,18 +294,6 @@ public class TaskStack {
return new Rect((int) (dockArea.left * width), (int) (dockArea.top * height),
(int) (dockArea.right * width), (int) (dockArea.bottom * height));
}
/**
* Returns the stack bounds with the given {@param width} and {@param height}.
*/
public Rect getStackBounds(Rect stackRect) {
int width = stackRect.width();
int height = stackRect.height();
return new Rect((int) (stackRect.left + stackArea.left * width),
(int) (stackRect.top + stackArea.top * height),
(int) (stackRect.left + (stackArea.right - stackArea.left) * width),
(int) (stackRect.top + (stackArea.bottom - stackArea.top) * height));
}
}
// The task offset to apply to a task id as a group affiliation
@@ -308,6 +326,29 @@ public class TaskStack {
}
}
/**
* Moves the given task to either the front of the freeform workspace or the stack.
*/
public void moveTaskToStack(Task task, int newStackId) {
// Find the index to insert into
ArrayList<Task> taskList = mTaskList.getTasks();
int taskCount = taskList.size();
if (!task.isFreeformTask() && (newStackId == FREEFORM_WORKSPACE_STACK_ID)) {
// Insert freeform tasks at the front
mTaskList.moveTaskToStack(task, taskCount, newStackId);
} else if (task.isFreeformTask() && (newStackId == FULLSCREEN_WORKSPACE_STACK_ID)) {
// Insert after the first stacked task
int insertIndex = 0;
for (int i = taskCount - 1; i >= 0; i--) {
if (!taskList.get(i).isFreeformTask()) {
insertIndex = i + 1;
break;
}
}
mTaskList.moveTaskToStack(task, insertIndex, newStackId);
}
}
/** Does the actual work associated with removing the task. */
void removeTaskImpl(Task t) {
// Remove the task from the list

View File

@@ -0,0 +1,29 @@
/*
* 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;
/**
* Represents a drop target for a drag gesture.
*/
public interface DropTarget {
/**
* Returns whether this target can accept this drop. The x,y are relative to the top level
* RecentsView, and the width/height are of the RecentsView.
*/
boolean acceptsDrop(int x, int y, int width, int height);
}

View File

@@ -111,6 +111,11 @@ public class FreeformWorkspaceLayoutAlgorithm {
Utilities.scaleRectAboutCenter(transformOut.rect, transformOut.scale);
transformOut.visible = true;
transformOut.p = 0;
if (DEBUG) {
Log.d(TAG, "getTransform: " + task.key + ", " + transformOut);
}
return transformOut;
}
return null;

View File

@@ -16,7 +16,6 @@
package com.android.systemui.recents.views;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.content.Context;
import android.graphics.Bitmap;
@@ -51,7 +50,7 @@ import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent;
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.misc.SystemServicesProxy;
@@ -314,9 +313,10 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
}
if (mDragView != null) {
Rect taskRect = mTaskStackView.mLayoutAlgorithm.mTaskRect;
mDragView.measure(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
MeasureSpec.makeMeasureSpec(taskRect.width(), MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(taskRect.height(), MeasureSpec.AT_MOST));
}
setMeasuredDimension(width, height);
@@ -765,52 +765,76 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
TaskStack.DockState.NONE.viewState.dockAreaAlpha);
}
public final void onBusEvent(DragDockStateChangedEvent event) {
if (event.dockState == TaskStack.DockState.NONE) {
public final void onBusEvent(DragDropTargetChangedEvent event) {
if (event.dropTarget == null || !(event.dropTarget instanceof TaskStack.DockState)) {
updateVisibleDockRegions(mTouchHandler.getDockStatesForCurrentOrientation(),
TaskStack.DockState.NONE.viewState.dockAreaAlpha);
} else {
updateVisibleDockRegions(new TaskStack.DockState[] {event.dockState}, -1);
final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
updateVisibleDockRegions(new TaskStack.DockState[] {dockState}, -1);
}
}
public final void onBusEvent(final DragEndEvent event) {
event.postAnimationTrigger.increment();
event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
final Runnable cleanUpRunnable = new Runnable() {
@Override
public void run() {
// Remove the drag view
removeView(mDragView);
mDragView = null;
updateVisibleDockRegions(null, -1);
// Dock the new task if we are hovering over a valid dock state
if (event.dockState != TaskStack.DockState.NONE) {
SystemServicesProxy ssp = Recents.getSystemServices();
ssp.startTaskInDockedMode(event.task.key.id, event.dockState.createMode);
launchTask(event.task, null, INVALID_STACK_ID);
}
}
});
if (event.dockState == TaskStack.DockState.NONE) {
// Animate the alpha back to what it was before
Rect taskBounds = mTaskStackView.getStackAlgorithm().getUntransformedTaskViewBounds();
int left = taskBounds.left + (int) ((1f - event.taskView.getScaleX()) * taskBounds.width()) / 2;
int top = taskBounds.top + (int) ((1f - event.taskView.getScaleY()) * taskBounds.height()) / 2;
};
// Animate the overlay alpha back to 0
updateVisibleDockRegions(null, -1);
if (event.dropTarget == null) {
// No drop targets for hit, so just animate the task back to its place
event.postAnimationTrigger.increment();
event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
cleanUpRunnable.run();
}
});
// Animate the task back to where it was before then clean up afterwards
TaskViewTransform taskTransform = new TaskViewTransform();
TaskStackLayoutAlgorithm layoutAlgorithm = mTaskStackView.getStackAlgorithm();
layoutAlgorithm.getStackTransform(event.task,
mTaskStackView.getScroller().getStackScroll(), taskTransform, null);
event.dragView.animate()
.scaleX(1f)
.scaleY(1f)
.translationX(left + event.taskView.getTranslationX())
.translationY(top + event.taskView.getTranslationY())
.scaleX(taskTransform.scale)
.scaleY(taskTransform.scale)
.translationX((layoutAlgorithm.mTaskRect.left - event.dragView.getLeft())
+ taskTransform.translationX)
.translationY((layoutAlgorithm.mTaskRect.top - event.dragView.getTop())
+ taskTransform.translationY)
.setDuration(175)
.setInterpolator(mFastOutSlowInInterpolator)
.withEndAction(event.postAnimationTrigger.decrementAsRunnable())
.start();
// Animate the overlay alpha back to 0
updateVisibleDockRegions(null, -1);
} else if (event.dropTarget instanceof TaskStack.DockState) {
final TaskStack.DockState dockState = (TaskStack.DockState) event.dropTarget;
// For now, just remove the drag view and the original task
// TODO: Animate the task to the drop target rect before launching it above
cleanUpRunnable.run();
// Dock the task and launch it
SystemServicesProxy ssp = Recents.getSystemServices();
ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode);
launchTask(event.task, null, INVALID_STACK_ID);
} else {
event.postAnimationTrigger.decrement();
// We dropped on another drop target, so just add the cleanup to the post animation
// trigger
event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
cleanUpRunnable.run();
}
});
}
}

View File

@@ -22,13 +22,16 @@ import android.view.MotionEvent;
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.DragDockStateChangedEvent;
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.ReferenceCountedTrigger;
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.
@@ -52,7 +55,10 @@ class DockRegion {
/**
* Handles touch events for a RecentsView.
*/
class RecentsViewTouchHandler {
public class RecentsViewTouchHandler {
private static final String TAG = "RecentsViewTouchHandler";
private static final boolean DEBUG = false;
private RecentsView mRv;
@@ -62,12 +68,21 @@ class RecentsViewTouchHandler {
private Point mDownPos = new Point();
private boolean mDragging;
private TaskStack.DockState mLastDockState = TaskStack.DockState.NONE;
private DropTarget mLastDropTarget;
private ArrayList<DropTarget> mDropTargets = new ArrayList<>();
public RecentsViewTouchHandler(RecentsView rv) {
mRv = rv;
}
/**
* 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.
*/
@@ -101,11 +116,24 @@ class RecentsViewTouchHandler {
mDragTask = event.task;
mTaskView = event.taskView;
mDragView = event.dragView;
mDropTargets.clear();
float x = mDownPos.x - mDragView.getTopLeftOffset().x;
float y = mDownPos.y - mDragView.getTopLeftOffset().y;
mDragView.setTranslationX(x);
mDragView.setTranslationY(y);
RecentsConfiguration config = Recents.getConfiguration();
if (!config.hasDockedTasks) {
// Add the dock state drop targets (these take priority)
TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation();
for (TaskStack.DockState dockState : dockStates) {
registerDropTargetForCurrentDrag(dockState);
}
}
// Request other drop targets to register themselves
EventBus.getDefault().send(new DragStartInitializeDropTargetsEvent(event.task, this));
}
public final void onBusEvent(DragEndEvent event) {
@@ -113,7 +141,7 @@ class RecentsViewTouchHandler {
mDragTask = null;
mTaskView = null;
mDragView = null;
mLastDockState = TaskStack.DockState.NONE;
mLastDropTarget = null;
}
/**
@@ -121,8 +149,6 @@ class RecentsViewTouchHandler {
* @param ev
*/
private void handleTouchEvent(MotionEvent ev) {
boolean isLandscape = mRv.getResources().getConfiguration().orientation ==
Configuration.ORIENTATION_LANDSCAPE;
int action = ev.getAction();
switch (action & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_DOWN:
@@ -137,20 +163,17 @@ class RecentsViewTouchHandler {
float x = evX - mDragView.getTopLeftOffset().x;
float y = evY - mDragView.getTopLeftOffset().y;
// Update the dock state
TaskStack.DockState[] dockStates = getDockStatesForCurrentOrientation();
TaskStack.DockState foundDockState = TaskStack.DockState.NONE;
for (int i = dockStates.length - 1; i >= 0; i--) {
TaskStack.DockState state = dockStates[i];
if (state.touchAreaContainsPoint(width, height, evX, evY)) {
foundDockState = state;
DropTarget currentDropTarget = null;
for (DropTarget target : mDropTargets) {
if (target.acceptsDrop((int) evX, (int) evY, width, height)) {
currentDropTarget = target;
break;
}
}
if (mLastDockState != foundDockState) {
mLastDockState = foundDockState;
EventBus.getDefault().send(new DragDockStateChangedEvent(mDragTask,
foundDockState));
if (mLastDropTarget != currentDropTarget) {
mLastDropTarget = currentDropTarget;
EventBus.getDefault().send(new DragDropTargetChangedEvent(mDragTask,
currentDropTarget));
}
mDragView.setTranslationX(x);
@@ -165,7 +188,7 @@ class RecentsViewTouchHandler {
mRv.getContext(), null, null, null);
postAnimationTrigger.increment();
EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView, mDragView,
mLastDockState, postAnimationTrigger));
mLastDropTarget, postAnimationTrigger));
postAnimationTrigger.decrement();
break;
}

View File

@@ -63,23 +63,6 @@ public class TaskStackLayoutAlgorithm {
Context mContext;
/*
+-------------------+
| SEARCH |
+-------------------+
|+-----------------+|
|| FREEFORM ||
|| ||
|| ||
|+-----------------+|
| +-----------+ |
| +---------------+ |
| | | |
|+-----------------+|
|| STACK ||
+-------------------+
*/
// The task bounds (untransformed) for layout. This rect is anchored at mTaskRoot.
public Rect mTaskRect = new Rect();
// The freeform workspace bounds, inset from the top by the search bar, and is a fixed height
@@ -165,8 +148,8 @@ public class TaskStackLayoutAlgorithm {
}, new ParametricCurve.ParametricCurveFunction() {
@Override
public float f(float p) {
// Don't scale when there are freeform tasks
if (mNumFreeformTasks > 0) {
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.hasFreeformWorkspaceSupport()) {
return 1f;
}
@@ -196,6 +179,7 @@ public class TaskStackLayoutAlgorithm {
*/
public void initialize(Rect taskStackBounds) {
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsConfiguration config = Recents.getConfiguration();
int widthPadding = (int) (config.taskStackWidthPaddingPct * taskStackBounds.width());
int heightPadding = mContext.getResources().getDimensionPixelSize(
@@ -262,9 +246,7 @@ public class TaskStackLayoutAlgorithm {
* in the stack.
*/
void update(TaskStack stack) {
if (DEBUG) {
Log.d(TAG, "update");
}
SystemServicesProxy ssp = Recents.getSystemServices();
// Clear the progress map
mTaskProgressMap.clear();
@@ -292,10 +274,10 @@ public class TaskStackLayoutAlgorithm {
mNumStackTasks = stackTasks.size();
mNumFreeformTasks = freeformTasks.size();
float pAtBackMostTaskTop = 0;
float pAtFrontMostTaskTop = pAtBackMostTaskTop;
if (!stackTasks.isEmpty()) {
// Update the for each task from back to front.
float pAtBackMostTaskTop = 0;
float pAtFrontMostTaskTop = pAtBackMostTaskTop;
int taskCount = stackTasks.size();
for (int i = 0; i < taskCount; i++) {
Task task = stackTasks.get(i);
@@ -307,7 +289,7 @@ public class TaskStackLayoutAlgorithm {
if (i < (taskCount - 1)) {
// Increment the peek height
float pPeek = task.group.isFrontMostTask(task) ?
float pPeek = task.group == null || task.group.isFrontMostTask(task) ?
mBetweenAffiliationPOffset : mWithinAffiliationPOffset;
pAtFrontMostTaskTop += pPeek;
}
@@ -318,10 +300,12 @@ public class TaskStackLayoutAlgorithm {
// Set the stack end scroll progress to the point at which the bottom of the front-most
// task is aligned to the bottom of the stack
mMaxScrollP = alignToStackBottom(pAtFrontMostTaskTop,
mStackBottomPOffset + mTaskHeightPOffset);
mStackBottomPOffset + (ssp.hasFreeformWorkspaceSupport() ?
mTaskHalfHeightPOffset : mTaskHeightPOffset));
// Basically align the back-most task such that the last two tasks would be visible
mMinScrollP = alignToStackBottom(pAtBackMostTaskTop,
mStackBottomPOffset + mTaskHeightPOffset);
mStackBottomPOffset + (ssp.hasFreeformWorkspaceSupport() ?
mTaskHalfHeightPOffset : mTaskHeightPOffset));
} else {
// When there is a single item, then just make all the stack progresses the same
mMinScrollP = mMaxScrollP = 0;
@@ -379,7 +363,7 @@ public class TaskStackLayoutAlgorithm {
if (progress < 0) {
break;
}
boolean isFrontMostTaskInGroup = task.group.isFrontMostTask(task);
boolean isFrontMostTaskInGroup = task.group == null || task.group.isFrontMostTask(task);
if (isFrontMostTaskInGroup) {
float scaleAtP = sCurve.pToScale(progress);
int scaleYOffsetAtP = (int) (((1f - scaleAtP) * taskHeight) / 2);
@@ -432,12 +416,13 @@ public class TaskStackLayoutAlgorithm {
/** Update/get the transform */
public TaskViewTransform getStackTransform(float taskProgress, float stackScroll,
TaskViewTransform transformOut, TaskViewTransform prevTransform) {
SystemServicesProxy ssp = Recents.getSystemServices();
if (mNumStackTasks == 1) {
if (!ssp.hasFreeformWorkspaceSupport() && mNumStackTasks == 1) {
// Center the task in the stack, changing the scale will not follow the curve, but just
// modulate some values directly
float pTaskRelative = mMinScrollP - stackScroll;
float scale = (mNumFreeformTasks > 0) ? 1f : SINGLE_TASK_SCALE;
float scale = ssp.hasFreeformWorkspaceSupport() ? 1f : SINGLE_TASK_SCALE;
int topOffset = (mCurrentStackRect.top - mTaskRect.top) +
(mCurrentStackRect.height() - mTaskRect.height()) / 2;
transformOut.scale = scale;

View File

@@ -30,6 +30,8 @@ import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.recents.Constants;
@@ -42,6 +44,10 @@ import com.android.systemui.recents.events.activity.PackagesChangedEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
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.events.ui.focus.DismissFocusedTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusNextTaskViewEvent;
import com.android.systemui.recents.events.ui.focus.FocusPreviousTaskViewEvent;
@@ -58,6 +64,8 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
@@ -110,8 +118,10 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
LayoutInflater mInflater;
boolean mLayersDisabled;
Interpolator mFastOutSlowInInterpolator;
// A convenience update listener to request updating clipping of tasks
ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
private ValueAnimator.AnimatorUpdateListener mRequestUpdateClippingListener =
new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
@@ -119,6 +129,21 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
};
// The drop targets for a task drag
private DropTarget mFreeformWorkspaceDropTarget = new DropTarget() {
@Override
public boolean acceptsDrop(int x, int y, int width, int height) {
return mLayoutAlgorithm.mFreeformRect.contains(x, y);
}
};
private DropTarget mStackDropTarget = new DropTarget() {
@Override
public boolean acceptsDrop(int x, int y, int width, int height) {
return mLayoutAlgorithm.mCurrentStackRect.contains(x, y);
}
};
public TaskStackView(Context context, TaskStack stack) {
super(context);
// Set the stack first
@@ -130,6 +155,8 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mStackScroller = new TaskStackViewScroller(context, mLayoutAlgorithm);
mStackScroller.setCallbacks(this);
mTouchHandler = new TaskStackViewTouchHandler(context, this, mStackScroller);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.fast_out_slow_in);
int taskBarDismissDozeDelaySeconds = getResources().getInteger(
R.integer.recents_task_bar_dismiss_delay_seconds);
@@ -390,7 +417,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Pick up all the freeform tasks
int firstVisStackIndex = isValidVisibleStackRange ? visibleStackRange[0] : 0;
for (int i = mStack.getTaskCount() - 1; i > firstVisStackIndex; i--) {
for (int i = mStack.getTaskCount() - 1; i >= firstVisStackIndex; i--) {
Task task = tasks.get(i);
if (!task.isFreeformTask()) {
continue;
@@ -411,6 +438,24 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
// Animate the task into place
tv.updateViewPropertiesToTaskTransform(transform,
mStackViewsAnimationDuration, mRequestUpdateClippingListener);
// Reattach it in the right z order
detachViewFromParent(tv);
int insertIndex = -1;
int taskIndex = mStack.indexOfTask(task);
taskViews = getTaskViews();
taskViewCount = taskViews.size();
for (int j = 0; j < taskViewCount; j++) {
Task tvTask = taskViews.get(j).getTask();
if (taskIndex < mStack.indexOfTask(tvTask)) {
insertIndex = j;
break;
}
}
attachViewToParent(tv, insertIndex, tv.getLayoutParams());
// Update the task views list after adding the new task view
updateTaskViewsList();
}
// Pick up all the newly visible children and update all the existing children
@@ -514,7 +559,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/** Updates the min and max virtual scroll bounds */
void updateMinMaxScroll(boolean boundScrollToNewMinMax) {
void updateLayout(boolean boundScrollToNewMinMax) {
// Compute the min and max scroll values
mLayoutAlgorithm.update(mStack);
@@ -700,21 +745,21 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mLayoutAlgorithm.initialize(taskStackBounds);
// Update the scroll bounds
updateMinMaxScroll(false);
updateLayout(false);
}
/**
* This is ONLY used from AlternateRecentsComponent to update the dummy stack view for purposes
* This is ONLY used from the Recents component to update the dummy stack view for purposes
* of getting the task rect to animate to.
*/
public void updateMinMaxScrollForStack(TaskStack stack) {
public void updateLayoutForStack(TaskStack stack) {
mStack = stack;
updateMinMaxScroll(false);
updateLayout(false);
}
/**
* Computes the maximum number of visible tasks and thumbnails. Requires that
* updateMinMaxScrollForStack() is called first.
* updateLayoutForStack() is called first.
*/
public TaskStackLayoutAlgorithm.VisibilityReport computeStackVisibilityReport() {
return mLayoutAlgorithm.computeStackVisibilityReport(mStack.getTasks());
@@ -1002,7 +1047,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Update the min/max scroll and animate other task views into their new positions
updateMinMaxScroll(true);
updateLayout(true);
if (wasFrontMostTask) {
// Since the max scroll progress is offset from the bottom of the stack, just scroll
@@ -1028,7 +1073,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
// Update the min/max scroll and animate other task views into their new positions
updateMinMaxScroll(true);
updateLayout(true);
// Animate all the tasks into place
requestSynchronizeStackViewsWithModel(200);
@@ -1088,7 +1133,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
// Scroll the item to the top of the stack (sans-peek) rect so that we can see it better
updateMinMaxScroll(false);
updateLayout(false);
float overlapHeight = mLayoutAlgorithm.getTaskOverlapHeight();
setStackScrollRaw((int) (newStack.indexOfTask(filteredTask) * overlapHeight));
boundScrollRaw();
@@ -1117,7 +1162,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
mLayoutAlgorithm.updateTaskOffsets(mStack.getTasks());
// Restore the stashed scroll
updateMinMaxScroll(false);
updateLayout(false);
setStackScrollRaw(mStashedScroll);
boundScrollRaw();
@@ -1305,6 +1350,79 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
public final void onBusEvent(DragStartEvent event) {
if (event.task.isFreeformTask()) {
// Animate to the front of the stack
mStackScroller.animateScroll(mStackScroller.getStackScroll(),
mLayoutAlgorithm.mInitialScrollP, null);
}
}
public final void onBusEvent(DragStartInitializeDropTargetsEvent event) {
SystemServicesProxy ssp = Recents.getSystemServices();
if (ssp.hasFreeformWorkspaceSupport()) {
event.handler.registerDropTargetForCurrentDrag(mStackDropTarget);
event.handler.registerDropTargetForCurrentDrag(mFreeformWorkspaceDropTarget);
}
}
public final void onBusEvent(DragDropTargetChangedEvent event) {
// TODO: Animate the freeform workspace background etc.
}
public final void onBusEvent(final DragEndEvent event) {
if (event.dropTarget != mFreeformWorkspaceDropTarget &&
event.dropTarget != mStackDropTarget) {
return;
}
if (event.task.isFreeformTask() && event.dropTarget == mFreeformWorkspaceDropTarget) {
// TODO: Animate back into view
return;
}
if (!event.task.isFreeformTask() && event.dropTarget == mStackDropTarget) {
// TODO: Animate back into view
return;
}
// Move the task to the right position in the stack (ie. the front of the stack if freeform
// or the front of the stack if fullscreen). Note, we MUST move the tasks before we update
// their stack ids, otherwise, the keys will have changed.
if (event.dropTarget == mFreeformWorkspaceDropTarget) {
mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
updateLayout(true);
} else if (event.dropTarget == mStackDropTarget) {
mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
updateLayout(true);
}
event.postAnimationTrigger.increment();
event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
SystemServicesProxy ssp = Recents.getSystemServices();
ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
}
});
// Animate the drag view to the new position
mLayoutAlgorithm.getStackTransform(event.task, mStackScroller.getStackScroll(),
mTmpTransform, null);
event.dragView.animate()
.scaleX(mTmpTransform.scale)
.scaleY(mTmpTransform.scale)
.translationX((mLayoutAlgorithm.mTaskRect.left - event.dragView.getLeft())
+ mTmpTransform.translationX)
.translationY((mLayoutAlgorithm.mTaskRect.top - event.dragView.getTop())
+ mTmpTransform.translationY)
.setDuration(175)
.setInterpolator(mFastOutSlowInInterpolator)
.withEndAction(event.postAnimationTrigger.decrementAsRunnable())
.start();
// Animate the other views into place
requestSynchronizeStackViewsWithModel(175);
}
/**
* Removes the task from the stack, and updates the focus to the next task in the stack if the
* removed TaskView was focused.

View File

@@ -51,6 +51,7 @@ import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.misc.SystemServicesProxy;
import com.android.systemui.recents.misc.Utilities;
import com.android.systemui.recents.model.Task;
import com.android.systemui.recents.model.TaskStack;
import com.android.systemui.statusbar.phone.PhoneStatusBar;
/* A task view */
@@ -708,6 +709,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
@Override
public void onTaskDataLoaded() {
SystemServicesProxy ssp = Recents.getSystemServices();
RecentsConfiguration config = Recents.getConfiguration();
if (mThumbnailView != null && mHeaderView != null) {
// Bind each of the views to the new task data
@@ -715,7 +717,14 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
mHeaderView.rebindToTask(mTask);
// Rebind any listeners
mActionButtonView.setOnClickListener(this);
setOnLongClickListener(config.hasDockedTasks ? null : this);
// Only enable long-click if we have a freeform workspace to drag to/from, or if we
// aren't already docked
if (ssp.hasFreeformWorkspaceSupport() || !config.hasDockedTasks) {
setOnLongClickListener(this);
} else {
setOnLongClickListener(null);
}
}
mTaskDataLoaded = true;
}
@@ -759,15 +768,21 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
// Start listening for drag events
setClipViewInStack(false);
final int width = (int) (getScaleX() * getWidth());
final int height = (int) (getScaleY() * getHeight());
final float finalScale = getScaleX() * 1.05f;
final int width = getWidth();
final int height = getHeight();
Bitmap dragBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(dragBitmap);
c.scale(getScaleX(), getScaleY());
mThumbnailView.draw(c);
mHeaderView.draw(c);
c.setBitmap(null);
// The downTouchPos is relative to the currently transformed TaskView, but we will be
// dragging a copy of the full task view, which makes it easier for us to animate them
// when the user drops
mDownTouchPos.x += ((1f - getScaleX()) * width) / 2;
mDownTouchPos.y += ((1f - getScaleY()) * height) / 2;
// Initiate the drag
final DragView dragView = new DragView(getContext(), dragBitmap, mDownTouchPos);
dragView.setOutlineProvider(new ViewOutlineProvider() {
@@ -776,6 +791,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
outline.setRect(0, 0, width, height);
}
});
dragView.setScaleX(getScaleX());
dragView.setScaleY(getScaleY());
dragView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
@Override
public void onViewAttachedToWindow(View v) {
@@ -785,8 +802,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
dragView.setElevation(getElevation());
dragView.setTranslationZ(getTranslationZ());
dragView.animate()
.scaleX(1.05f)
.scaleY(1.05f)
.scaleX(finalScale)
.scaleY(finalScale)
.setDuration(175)
.setInterpolator(mFastOutSlowInInterpolator)
.start();
@@ -807,18 +824,19 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks,
/**** Events ****/
public final void onBusEvent(DragEndEvent event) {
event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
// If docked state == null:
// Animate the drag view back from where it is, to the view location, then after it returns,
// update the clip state
setClipViewInStack(true);
if (!(event.dropTarget instanceof TaskStack.DockState)) {
event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() {
@Override
public void run() {
// Show this task view
setVisibility(View.VISIBLE);
// Show this task view
setVisibility(View.VISIBLE);
}
});
// Animate the drag view back from where it is, to the view location, then after
// it returns, update the clip state
setClipViewInStack(true);
}
});
}
EventBus.getDefault().unregister(this);
}
}