Fix exception when docking task.

- If a task fails to dock, animate the stack back to original state so 
  that the layout is not stuck in a "docked" state.

Bug: 28577229
Change-Id: If927b898a48cd5949764cb3b0c0798d22efd850a
This commit is contained in:
Winson
2016-05-05 16:16:50 -07:00
committed by Winson Chung
parent 7e2d589ce0
commit 27c28f8da2
8 changed files with 185 additions and 92 deletions

View File

@@ -70,7 +70,6 @@ import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
import com.android.systemui.recents.events.ui.StackViewScrolledEvent;
import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEvent;
import com.android.systemui.recents.events.ui.UserInteractionEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
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;
@@ -90,7 +89,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
/**
* The main Recents activity that is started from AlternateRecentsComponent.
* The main Recents activity that is started from RecentsComponent.
*/
public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreDrawListener {
@@ -760,13 +759,6 @@ public class RecentsActivity extends Activity implements ViewTreeObserver.OnPreD
mIgnoreAltTabRelease = true;
}
public final void onBusEvent(final DragEndEvent event) {
// Handle the case where we drop onto a dock region
if (event.dropTarget instanceof TaskStack.DockState) {
mScrimViews.animateScrimToCurrentNavBarState(false /* hasStackTasks */);
}
}
public final void onBusEvent(final DockedTopTaskEvent event) {
mRecentsView.getViewTreeObserver().addOnPreDrawListener(mRecentsDrawnEventListener);
mRecentsView.invalidate();

View File

@@ -0,0 +1,39 @@
/*
* 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.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;
import com.android.systemui.recents.views.TaskView;
/**
* This event is sent whenever a drag end is cancelled because of an error.
*/
public class DragEndCancelledEvent extends EventBus.AnimatedEvent {
public final TaskStack stack;
public final Task task;
public final TaskView taskView;
public DragEndCancelledEvent(TaskStack stack, Task task, TaskView taskView) {
this.stack = stack;
this.task = task;
this.taskView = taskView;
}
}

View File

@@ -393,18 +393,19 @@ public class SystemServicesProxy {
}
/** Docks a task to the side of the screen and starts it. */
public void startTaskInDockedMode(int taskId, int createMode) {
if (mIam == null) return;
public boolean startTaskInDockedMode(int taskId, int createMode) {
if (mIam == null) return false;
try {
// TODO: Determine what animation we want for the incoming task
final ActivityOptions options = ActivityOptions.makeBasic();
options.setDockCreateMode(createMode);
options.setLaunchStackId(DOCKED_STACK_ID);
mIam.startActivityFromRecents(taskId, options.toBundle());
} catch (RemoteException e) {
return true;
} catch (RemoteException | IllegalArgumentException e) {
e.printStackTrace();
}
return false;
}
/** Docks an already resumed task to the side of the screen. */

View File

@@ -225,6 +225,18 @@ public class Utilities {
animator.removeAllListeners();
}
/**
* Sets the given {@link View}'s frame from its current translation.
*/
public static void setViewFrameFromTranslation(View v) {
RectF taskViewRect = new RectF(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
taskViewRect.offset(v.getTranslationX(), v.getTranslationY());
v.setTranslationX(0);
v.setTranslationY(0);
v.setLeftTopRightBottom((int) taskViewRect.left, (int) taskViewRect.top,
(int) taskViewRect.right, (int) taskViewRect.bottom);
}
/**
* Returns a view stub for the given view id.
*/

View File

@@ -64,6 +64,7 @@ import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEndedEvent;
import com.android.systemui.recents.events.ui.DraggingInRecentsEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
@@ -510,45 +511,43 @@ public class RecentsView extends FrameLayout {
// We translated the view but we need to animate it back from the current layout-space
// rect to its final layout-space rect
int x = (int) event.taskView.getTranslationX();
int y = (int) event.taskView.getTranslationY();
Rect taskViewRect = new Rect(event.taskView.getLeft(), event.taskView.getTop(),
event.taskView.getRight(), event.taskView.getBottom());
taskViewRect.offset(x, y);
event.taskView.setTranslationX(0);
event.taskView.setTranslationY(0);
event.taskView.setLeftTopRightBottom(taskViewRect.left, taskViewRect.top,
taskViewRect.right, taskViewRect.bottom);
final OnAnimationStartedListener startedListener = new OnAnimationStartedListener() {
@Override
public void onAnimationStarted() {
EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
// Remove the task and don't bother relaying out, as all the tasks will be
// relaid out when the stack changes on the multiwindow change event
mTaskStackView.getStack().removeTask(event.task, null,
true /* fromDockGesture */);
}
};
Utilities.setViewFrameFromTranslation(event.taskView);
// Dock the task and launch it
SystemServicesProxy ssp = Recents.getSystemServices();
ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode);
final Rect taskRect = getTaskRect(event.taskView);
IAppTransitionAnimationSpecsFuture future = mTransitionHelper.getAppTransitionFuture(
new AnimationSpecComposer() {
@Override
public List<AppTransitionAnimationSpec> composeSpecs() {
return mTransitionHelper.composeDockAnimationSpec(
event.taskView, taskRect);
}
});
ssp.overridePendingAppTransitionMultiThumbFuture(future,
mTransitionHelper.wrapStartedListener(startedListener),
true /* scaleUp */);
if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) {
final OnAnimationStartedListener startedListener =
new OnAnimationStartedListener() {
@Override
public void onAnimationStarted() {
EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
// Remove the task and don't bother relaying out, as all the tasks will be
// relaid out when the stack changes on the multiwindow change event
mTaskStackView.getStack().removeTask(event.task, null,
true /* fromDockGesture */);
}
};
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
event.task.getTopComponent().flattenToShortString());
final Rect taskRect = getTaskRect(event.taskView);
IAppTransitionAnimationSpecsFuture future =
mTransitionHelper.getAppTransitionFuture(
new AnimationSpecComposer() {
@Override
public List<AppTransitionAnimationSpec> composeSpecs() {
return mTransitionHelper.composeDockAnimationSpec(
event.taskView, taskRect);
}
});
ssp.overridePendingAppTransitionMultiThumbFuture(future,
mTransitionHelper.wrapStartedListener(startedListener),
true /* scaleUp */);
MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
event.task.getTopComponent().flattenToShortString());
} else {
EventBus.getDefault().send(new DragEndCancelledEvent(mStack, event.task,
event.taskView));
}
} else {
// Animate the overlay alpha back to 0
updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
@@ -565,6 +564,12 @@ public class RecentsView extends FrameLayout {
}
}
public final void onBusEvent(final DragEndCancelledEvent event) {
// Animate the overlay alpha back to 0
updateVisibleDockRegions(null, true /* isDefaultDockState */, -1, -1,
true /* animateAlpha */, false /* animateBounds */);
}
private Rect getTaskRect(TaskView taskView) {
int[] location = taskView.getLocationOnScreen();
int viewX = location[0];

View File

@@ -28,6 +28,9 @@ import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimatio
import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationCompletedEvent;
import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
import com.android.systemui.recents.events.activity.MultiWindowStateChangedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.model.TaskStack;
/** Manages the scrims for the various system bars. */
public class SystemBarScrimViews {
@@ -154,10 +157,22 @@ public class SystemBarScrimViews {
animateScrimToCurrentNavBarState(event.stack.getStackTaskCount() > 0);
}
public final void onBusEvent(final DragEndEvent event) {
// Hide the nav bar scrims once we drop to a dock region
if (event.dropTarget instanceof TaskStack.DockState) {
animateScrimToCurrentNavBarState(false /* hasStackTasks */);
}
}
public final void onBusEvent(final DragEndCancelledEvent event) {
// Restore the scrims to the normal state
animateScrimToCurrentNavBarState(event.stack.getStackTaskCount() > 0);
}
/**
* Animates the scrim to match the state of the current nav bar.
*/
public void animateScrimToCurrentNavBarState(boolean hasStackTasks) {
private void animateScrimToCurrentNavBarState(boolean hasStackTasks) {
boolean hasNavBarScrim = isNavBarScrimRequired(hasStackTasks);
if (mHasNavBarScrim != hasNavBarScrim) {
AnimationProps animation = hasNavBarScrim

View File

@@ -79,6 +79,7 @@ import com.android.systemui.recents.events.ui.UpdateFreeformTaskViewVisibilityEv
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.DragEndCancelledEvent;
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;
@@ -640,19 +641,23 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
/**
* @see #relayoutTaskViews(AnimationProps, boolean)
* @see #relayoutTaskViews(AnimationProps, ArrayMap<Task, AnimationProps>, boolean)
*/
public void relayoutTaskViews(AnimationProps animation) {
relayoutTaskViews(animation, false /* ignoreTaskOverrides */);
relayoutTaskViews(animation, null /* animationOverrides */,
false /* ignoreTaskOverrides */);
}
/**
* Relayout the the visible {@link TaskView}s to their current transforms as specified by the
* {@link TaskStackLayoutAlgorithm} with the given {@param animation}. This call cancels any
* animations that are current running on those task views, and will ensure that the children
* {@link TaskView}s will match the set of visible tasks in the stack.
* {@link TaskView}s will match the set of visible tasks in the stack. If a {@link Task} has
* an animation provided in {@param animationOverrides}, that will be used instead.
*/
private void relayoutTaskViews(AnimationProps animation, boolean ignoreTaskOverrides) {
private void relayoutTaskViews(AnimationProps animation,
ArrayMap<Task, AnimationProps> animationOverrides,
boolean ignoreTaskOverrides) {
// If we had a deferred animation, cancel that
cancelDeferredTaskViewLayoutAnimation();
@@ -665,13 +670,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
int taskViewCount = taskViews.size();
for (int i = 0; i < taskViewCount; i++) {
TaskView tv = taskViews.get(i);
int taskIndex = mStack.indexOfStackTask(tv.getTask());
Task task = tv.getTask();
int taskIndex = mStack.indexOfStackTask(task);
TaskViewTransform transform = mCurrentTaskTransforms.get(taskIndex);
if (mIgnoreTasks.contains(tv.getTask().key)) {
if (mIgnoreTasks.contains(task.key)) {
continue;
}
if (animationOverrides != null && animationOverrides.containsKey(task)) {
animation = animationOverrides.get(task);
}
updateTaskViewToTransform(tv, transform, animation);
}
}
@@ -829,6 +839,18 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
}
}
/**
* Updates the stack layout to its stable places.
*/
private void updateLayoutToStableBounds() {
mWindowRect.set(mStableWindowRect);
mStackBounds.set(mStableStackBounds);
mLayoutAlgorithm.setSystemInsets(mStableLayoutAlgorithm.mSystemInsets);
mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
updateLayoutAlgorithm(true /* boundScroll */);
}
/** Returns the scroller. */
public TaskStackViewScroller getScroller() {
return mStackScroller;
@@ -1820,16 +1842,11 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
} else {
// Restore the pre-drag task stack bounds, but ensure that we don't layout the dragging
// task view, so add it back to the ignore set after updating the layout
mWindowRect.set(mStableWindowRect);
mStackBounds.set(mStableStackBounds);
removeIgnoreTask(event.task);
mLayoutAlgorithm.setSystemInsets(mStableLayoutAlgorithm.mSystemInsets);
mLayoutAlgorithm.initialize(mDisplayRect, mWindowRect, mStackBounds,
TaskStackLayoutAlgorithm.StackState.getStackStateForStack(mStack));
updateLayoutAlgorithm(true /* boundScroll */);
updateLayoutToStableBounds();
addIgnoreTask(event.task);
}
relayoutTaskViews(animation, ignoreTaskOverrides);
relayoutTaskViews(animation, null /* animationOverrides */, ignoreTaskOverrides);
}
public final void onBusEvent(final DragEndEvent event) {
@@ -1867,30 +1884,38 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
});
}
// We translated the view but we need to animate it back from the current layout-space rect
// to its final layout-space rect
int x = (int) event.taskView.getTranslationX();
int y = (int) event.taskView.getTranslationY();
Rect taskViewRect = new Rect(event.taskView.getLeft(), event.taskView.getTop(),
event.taskView.getRight(), event.taskView.getBottom());
taskViewRect.offset(x, y);
event.taskView.setTranslationX(0);
event.taskView.setTranslationY(0);
event.taskView.setLeftTopRightBottom(taskViewRect.left, taskViewRect.top,
taskViewRect.right, taskViewRect.bottom);
// Animate the non-drag TaskViews back into position
mLayoutAlgorithm.getStackTransform(event.task, getScroller().getStackScroll(),
mTmpTransform, null);
event.getAnimationTrigger().increment();
relayoutTaskViews(new AnimationProps(DEFAULT_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN));
// Animate the drag TaskView back into position
updateTaskViewToTransform(event.taskView, mTmpTransform,
new AnimationProps(DEFAULT_SYNC_STACK_DURATION, Interpolators.FAST_OUT_SLOW_IN,
event.getAnimationTrigger().decrementOnAnimationEnd()));
// Restore the task, so that relayout will apply to it below
removeIgnoreTask(event.task);
// Convert the dragging task view back to its final layout-space rect
Utilities.setViewFrameFromTranslation(event.taskView);
// Animate all the tasks into place
ArrayMap<Task, AnimationProps> animationOverrides = new ArrayMap<>();
animationOverrides.put(event.task, new AnimationProps(SLOW_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN,
event.getAnimationTrigger().decrementOnAnimationEnd()));
relayoutTaskViews(new AnimationProps(SLOW_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN));
event.getAnimationTrigger().increment();
}
public final void onBusEvent(final DragEndCancelledEvent event) {
// Restore the pre-drag task stack bounds, including the dragging task view
removeIgnoreTask(event.task);
updateLayoutToStableBounds();
// Convert the dragging task view back to its final layout-space rect
Utilities.setViewFrameFromTranslation(event.taskView);
// Animate all the tasks into place
ArrayMap<Task, AnimationProps> animationOverrides = new ArrayMap<>();
animationOverrides.put(event.task, new AnimationProps(SLOW_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN,
event.getAnimationTrigger().decrementOnAnimationEnd()));
relayoutTaskViews(new AnimationProps(SLOW_SYNC_STACK_DURATION,
Interpolators.FAST_OUT_SLOW_IN));
event.getAnimationTrigger().increment();
}
public final void onBusEvent(IterateRecentsEvent event) {

View File

@@ -49,6 +49,7 @@ import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.LaunchTaskEvent;
import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.TaskViewDismissedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndCancelledEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
import com.android.systemui.recents.misc.ReferenceCountedTrigger;
@@ -695,15 +696,18 @@ public class TaskView extends FixedSizeFrameLayout implements Task.TaskCallbacks
public final void onBusEvent(DragEndEvent event) {
if (!(event.dropTarget instanceof TaskStack.DockState)) {
event.addPostAnimationCallback(new Runnable() {
@Override
public void run() {
// Animate the drag view back from where it is, to the view location, then after
// it returns, update the clip state
setClipViewInStack(true);
}
event.addPostAnimationCallback(() -> {
// Reset the clip state for the drag view after the end animation completes
setClipViewInStack(true);
});
}
EventBus.getDefault().unregister(this);
}
public final void onBusEvent(DragEndCancelledEvent event) {
// Reset the clip state for the drag view after the cancel animation completes
event.addPostAnimationCallback(() -> {
setClipViewInStack(true);
});
}
}