diff options
14 files changed, 627 insertions, 3 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 5c61c5ca66a2..284d54094d09 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java @@ -26,6 +26,7 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.pm.ActivityInfo; import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; @@ -41,6 +42,8 @@ import com.android.systemui.recents.events.activity.AppWidgetProviderChangedEven import com.android.systemui.recents.events.ui.DismissTaskEvent; import com.android.systemui.recents.events.ui.ResizeTaskEvent; import com.android.systemui.recents.events.ui.ShowApplicationInfoEvent; +import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent; +import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent; import com.android.systemui.recents.misc.Console; import com.android.systemui.recents.misc.ReferenceCountedTrigger; import com.android.systemui.recents.misc.SystemServicesProxy; @@ -612,6 +615,18 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView getResizeTaskDebugDialog().showResizeTaskDialog(event.task, mRecentsView); } + public final void onBusEvent(DragStartEvent event) { + // Lock the orientation while dragging + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LOCKED); + + // TODO: docking requires custom accessibility actions + } + + public final void onBusEvent(DragEndEvent event) { + // Unlock the orientation when dragging completes + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_BEHIND); + } + private void refreshSearchWidgetView() { if (mSearchWidgetInfo != null) { SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java index 52b9521b9952..59c590cd26f9 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java +++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsConfiguration.java @@ -22,6 +22,7 @@ import android.graphics.Rect; import android.provider.Settings; import com.android.systemui.R; import com.android.systemui.recents.misc.SystemServicesProxy; +import com.android.systemui.recents.model.RecentsTaskLoader; /** * Application resources that can be retrieved from the application context and are not specifically @@ -70,6 +71,7 @@ public class RecentsConfiguration { public final int smallestWidth; /** Misc **/ + public boolean hasDockedTasks; public boolean useHardwareLayers; public boolean fakeShadows; public int svelteLevel; @@ -114,6 +116,7 @@ public class RecentsConfiguration { lockToAppEnabled = ssp.getSystemSetting(context, Settings.System.LOCK_TO_APP_ENABLED) != 0; multiWindowEnabled = "true".equals(ssp.getSystemProperty("persist.sys.debug.multi_window")); + hasDockedTasks = ssp.hasDockedTask(); // Recompute some values based on the given state, since we can not rely on the resource // system to get certain values. diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDockStateChangedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDockStateChangedEvent.java new file mode 100644 index 000000000000..f2c3c338383e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragDockStateChangedEvent.java @@ -0,0 +1,35 @@ +/* + * 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.model.TaskStack; + +/** + * This event is sent when a user drag enters or exits a dock region. + */ +public class DragDockStateChangedEvent extends EventBus.Event { + + public final Task task; + public final TaskStack.DockState dockState; + + public DragDockStateChangedEvent(Task task, TaskStack.DockState dockState) { + this.task = task; + this.dockState = dockState; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java new file mode 100644 index 000000000000..827998d8529f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragEndEvent.java @@ -0,0 +1,45 @@ +/* + * 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.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.TaskView; + +/** + * This event is sent whenever a drag ends. + */ +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 ReferenceCountedTrigger postAnimationTrigger; + + public DragEndEvent(Task task, TaskView taskView, DragView dragView, + TaskStack.DockState dockState, ReferenceCountedTrigger postAnimationTrigger) { + this.task = task; + this.taskView = taskView; + this.dragView = dragView; + this.dockState = dockState; + this.postAnimationTrigger = postAnimationTrigger; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java new file mode 100644 index 000000000000..2d42a0e0103d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/events/ui/dragndrop/DragStartEvent.java @@ -0,0 +1,38 @@ +/* + * 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.DragView; +import com.android.systemui.recents.views.TaskView; + +/** + * This event is sent whenever a drag starts. + */ +public class DragStartEvent extends EventBus.Event { + + public final Task task; + public final TaskView taskView; + public final DragView dragView; + + public DragStartEvent(Task task, TaskView taskView, DragView dragView) { + this.task = task; + this.taskView = taskView; + this.dragView = dragView; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java index e0f820d400b6..119a6f2942fb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/SystemServicesProxy.java @@ -327,6 +327,21 @@ public class SystemServicesProxy { return mAm.isInHomeStack(taskId); } + /** + * @return whether there are any docked tasks. + */ + public boolean hasDockedTask() { + if (mIam == null) return false; + + ActivityManager.StackInfo stackInfo = null; + try { + stackInfo = mIam.getStackInfo(ActivityManager.DOCKED_STACK_ID); + } catch (RemoteException e) { + e.printStackTrace(); + } + return stackInfo != null; + } + /** Returns the top task thumbnail for the given task id */ public Bitmap getTaskThumbnail(int taskId) { if (mAm == null) return null; diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java index e810410cd8bd..93c5ee7c873e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java +++ b/packages/SystemUI/src/com/android/systemui/recents/misc/Utilities.java @@ -21,12 +21,29 @@ import android.graphics.Color; import android.graphics.Matrix; import android.graphics.Rect; import android.view.View; +import android.view.ViewParent; import java.util.ArrayList; /* Common code */ public class Utilities { + /** + * @return the first parent walking up the view hierarchy that has the given class type. + * + * @param parentClass must be a class derived from {@link View} + */ + public static <T extends View> T findParent(View v, Class<T> parentClass) { + ViewParent parent = v.getParent(); + while (parent != null) { + if (parent.getClass().equals(parentClass)) { + return (T) parent; + } + parent = parent.getParent(); + } + return null; + } + /** Scales a rect about its centroid */ public static void scaleRectAboutCenter(Rect r, float scale) { if (scale != 1.0f) { diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java index 8eec87ee8f76..0fb235b9e23a 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java +++ b/packages/SystemUI/src/com/android/systemui/recents/model/TaskStack.java @@ -16,8 +16,11 @@ package com.android.systemui.recents.model; +import android.app.ActivityManager; import android.content.Context; import android.graphics.Color; +import android.graphics.Rect; +import android.graphics.RectF; import com.android.systemui.R; import com.android.systemui.recents.Constants; import com.android.systemui.recents.misc.NamedCounter; @@ -30,6 +33,9 @@ import java.util.HashMap; import java.util.List; 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; + /** * An interface for a task filter to query whether a particular task should show in a stack. @@ -174,6 +180,63 @@ public class TaskStack { public void onStackUnfiltered(TaskStack newStack, ArrayList<Task> curTasks); } + + public enum DockState { + LEFT(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, + new RectF(0, 0, 0.25f, 1), new RectF(0, 0, 0.5f, 1), new RectF(0.5f, 0, 1, 1)), + TOP(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT, + new RectF(0, 0, 1, 0.25f), new RectF(0, 0, 1, 0.5f), new RectF(0, 0.5f, 1, 1)), + RIGHT(DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, + new RectF(0.75f, 0, 1, 1), new RectF(0.5f, 0, 1, 1), new RectF(0, 0, 0.5f, 1)), + BOTTOM(DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT, + new RectF(0, 0.75f, 1, 1), new RectF(0, 0.5f, 1, 1), new RectF(0, 0, 1, 0.5f)); + + public final int createMode; + private final RectF touchArea; + private final RectF dockArea; + 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 + */ + DockState(int createMode, RectF touchArea, RectF dockArea, RectF stackArea) { + this.createMode = createMode; + this.touchArea = touchArea; + this.dockArea = dockArea; + this.stackArea = stackArea; + } + + /** + * Returns whether {@param x} and {@param y} are contained in the touch area scaled to the + * given {@param width} and {@param height}. + */ + public boolean touchAreaContainsPoint(int width, int height, float x, float y) { + int left = (int) (touchArea.left * width); + int top = (int) (touchArea.top * height); + int right = (int) (touchArea.right * width); + int bottom = (int) (touchArea.bottom * height); + return x >= left && y >= top && x <= right && y <= bottom; + } + + /** + * Returns the docked task bounds with the given {@param width} and {@param height}. + */ + public Rect getDockedBounds(int width, int height) { + 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(int width, int height) { + return new Rect((int) (stackArea.left * width), (int) (stackArea.top * height), + (int) (stackArea.right * width), (int) (stackArea.bottom * height)); + } + } + // The task offset to apply to a task id as a group affiliation static final int IndividualTaskIdOffset = 1 << 16; diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java b/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java new file mode 100644 index 000000000000..96dfaac360c3 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/DragView.java @@ -0,0 +1,39 @@ +/* + * 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.Context; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.widget.ImageView; + +public class DragView extends ImageView { + + // The offset from the top-left of the dragBitmap + Point mTopLeftOffset; + + public DragView(Context context, Bitmap dragBitmap, Point tlOffset) { + super(context); + + mTopLeftOffset = tlOffset; + setImageBitmap(dragBitmap); + } + + public Point getTopLeftOffset() { + return mTopLeftOffset; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index de8730dd8be7..e4061553e631 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -22,6 +22,7 @@ import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Rect; +import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.os.IRemoteCallback; import android.os.RemoteException; @@ -30,19 +31,25 @@ import android.util.Log; import android.util.SparseArray; import android.view.AppTransitionAnimationSpec; import android.view.LayoutInflater; +import android.view.MotionEvent; import android.view.View; import android.view.WindowInsets; import android.view.WindowManagerGlobal; +import android.view.animation.AccelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; import android.widget.FrameLayout; import com.android.internal.logging.MetricsLogger; import com.android.systemui.R; import com.android.systemui.recents.Constants; +import com.android.systemui.recents.RecentsActivity; import com.android.systemui.recents.RecentsAppWidgetHostView; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.ui.DismissTaskEvent; +import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent; +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; import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; @@ -78,6 +85,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV TaskStackView mTaskStackView; RecentsAppWidgetHostView mSearchBar; RecentsViewCallbacks mCb; + + RecentsViewTouchHandler mTouchHandler; + DragView mDragView; + ColorDrawable mDockRegionOverlay; + Interpolator mFastOutSlowInInterpolator; Rect mSystemInsets = new Rect(); @@ -96,10 +108,14 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV public RecentsView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + setWillNotDraw(false); mConfig = RecentsConfiguration.getInstance(); mInflater = LayoutInflater.from(context); mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context, com.android.internal.R.interpolator.fast_out_slow_in); + mTouchHandler = new RecentsViewTouchHandler(this); + mDockRegionOverlay = new ColorDrawable(0x66000000); + mDockRegionOverlay.setAlpha(0); } /** Sets the callbacks */ @@ -200,6 +216,7 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV ArrayList<Task> tasks = stack.getTasks(); // Find the launch task in the stack + // TODO: replace this with an event from RecentsActivity if (!tasks.isEmpty()) { int taskCount = tasks.size(); for (int j = 0; j < taskCount; j++) { @@ -267,6 +284,20 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } } + @Override + protected void onAttachedToWindow() { + EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); + EventBus.getDefault().register(mTouchHandler, RecentsActivity.EVENT_BUS_PRIORITY + 1); + super.onAttachedToWindow(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + EventBus.getDefault().unregister(this); + EventBus.getDefault().unregister(mTouchHandler); + } + /** * This is called with the full size of the window since we are handling our own insets. */ @@ -293,6 +324,12 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV mTaskStackView.measure(widthMeasureSpec, heightMeasureSpec); } + if (mDragView != null) { + mDragView.measure( + MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), + MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST)); + } + setMeasuredDimension(width, height); } @@ -314,6 +351,11 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV if (mTaskStackView != null && mTaskStackView.getVisibility() != GONE) { mTaskStackView.layout(left, top, left + getMeasuredWidth(), top + getMeasuredHeight()); } + + if (mDragView != null) { + mDragView.layout(left, top, left + mDragView.getMeasuredWidth(), + top + mDragView.getMeasuredHeight()); + } } @Override @@ -323,6 +365,24 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV return insets.consumeSystemWindowInsets(); } + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + return mTouchHandler.onInterceptTouchEvent(ev); + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + return mTouchHandler.onTouchEvent(ev); + } + + @Override + protected void dispatchDraw(Canvas canvas) { + super.dispatchDraw(canvas); + if (mDockRegionOverlay.getAlpha() > 0) { + mDockRegionOverlay.draw(canvas); + } + } + /** Notifies each task view of the user interaction. */ public void onUserInteraction() { // Get the first stack view @@ -697,4 +757,64 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV .start(); } } + + /**** EventBus Events ****/ + + public final void onBusEvent(DragStartEvent event) { + // Add the drag view + mDragView = event.dragView; + addView(mDragView); + } + + public final void onBusEvent(DragDockStateChangedEvent event) { + // Update the task stack positions, and then + if (event.dockState != null) { + // Draw an overlay on the bounds of the dock task + mDockRegionOverlay.setBounds( + event.dockState.getDockedBounds(getMeasuredWidth(), getMeasuredHeight())); + mDockRegionOverlay.setAlpha(255); + } else { + mDockRegionOverlay.setAlpha(0); + } + invalidate(); + } + + public final void onBusEvent(final DragEndEvent event) { + event.postAnimationTrigger.increment(); + event.postAnimationTrigger.addLastDecrementRunnable(new Runnable() { + @Override + public void run() { + // Remove the drag view + removeView(mDragView); + mDragView = null; + mDockRegionOverlay.setAlpha(0); + invalidate(); + + // Dock the new task if we are hovering over a valid dock state + if (event.dockState != null) { + SystemServicesProxy ssp = RecentsTaskLoader.getInstance().getSystemServicesProxy(); + ssp.setTaskResizeable(event.task.key.id); + ssp.dockTask(event.task.key.id, event.dockState.createMode); + launchTask(event.task, null); + } + } + }); + if (event.dockState == null) { + // 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; + event.dragView.animate() + .alpha(1f) + .translationX(left + event.taskView.getTranslationX()) + .translationY(top + event.taskView.getTranslationY()) + .setDuration(175) + .setInterpolator(new AccelerateInterpolator(1.5f)) + .withEndAction(event.postAnimationTrigger.decrementAsRunnable()) + .withLayer() + .start(); + } else { + event.postAnimationTrigger.decrement(); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java new file mode 100644 index 000000000000..8dea0cd1ab38 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsViewTouchHandler.java @@ -0,0 +1,149 @@ +/* + * 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 com.android.systemui.recents.events.EventBus; +import com.android.systemui.recents.events.ui.dragndrop.DragDockStateChangedEvent; +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; +import com.android.systemui.recents.model.Task; +import com.android.systemui.recents.model.TaskStack; + + +/** + * Represents the dock regions for each orientation. + */ +class DockRegion { + public static TaskStack.DockState[] LANDSCAPE = { + TaskStack.DockState.LEFT, TaskStack.DockState.RIGHT + }; + public static TaskStack.DockState[] PORTRAIT = { + TaskStack.DockState.TOP, TaskStack.DockState.BOTTOM + }; +} + +/** + * Handles touch events for a RecentsView. + */ +class RecentsViewTouchHandler { + + private RecentsView mRv; + + private Task mDragTask; + private TaskView mTaskView; + private DragView mDragView; + + private Point mDownPos = new Point(); + private boolean mDragging; + private TaskStack.DockState mLastDockState; + + public RecentsViewTouchHandler(RecentsView rv) { + mRv = rv; + } + + /** Touch preprocessing for handling below */ + public boolean onInterceptTouchEvent(MotionEvent ev) { + int action = ev.getAction(); + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + mDownPos.set((int) ev.getX(), (int) ev.getY()); + break; + } + return mDragging; + } + + /** Handles touch events once we have intercepted them */ + public boolean onTouchEvent(MotionEvent ev) { + if (!mDragging) return false; + + boolean isLandscape = mRv.getResources().getConfiguration().orientation == + Configuration.ORIENTATION_LANDSCAPE; + 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: { + int width = mRv.getMeasuredWidth(); + int height = mRv.getMeasuredHeight(); + float evX = ev.getX(); + float evY = ev.getY(); + float x = evX - mDragView.getTopLeftOffset().x; + float y = evY - mDragView.getTopLeftOffset().y; + + // Update the dock state + TaskStack.DockState[] dockStates = isLandscape ? + DockRegion.LANDSCAPE : DockRegion.PORTRAIT; + TaskStack.DockState foundDockState = null; + for (int i = 0; i < dockStates.length; i++) { + TaskStack.DockState state = dockStates[i]; + if (state.touchAreaContainsPoint(width, height, evX, evY)) { + foundDockState = state; + break; + } + } + if (mLastDockState != foundDockState) { + mLastDockState = foundDockState; + EventBus.getDefault().send(new DragDockStateChangedEvent(mDragTask, + foundDockState)); + } + + mDragView.setTranslationX(x); + mDragView.setTranslationY(y); + break; + } + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: { + ReferenceCountedTrigger postAnimationTrigger = new ReferenceCountedTrigger( + mRv.getContext(), null, null, null); + postAnimationTrigger.increment(); + EventBus.getDefault().send(new DragEndEvent(mDragTask, mTaskView, mDragView, + mLastDockState, postAnimationTrigger)); + postAnimationTrigger.decrement(); + break; + } + } + return true; + } + + /**** Events ****/ + + public final void onBusEvent(DragStartEvent event) { + mRv.getParent().requestDisallowInterceptTouchEvent(true); + mDragging = true; + mDragTask = event.task; + mTaskView = event.taskView; + mDragView = event.dragView; + + float x = mDownPos.x - mDragView.getTopLeftOffset().x; + float y = mDownPos.y - mDragView.getTopLeftOffset().y; + mDragView.setTranslationX(x); + mDragView.setTranslationY(y); + } + + public final void onBusEvent(DragEndEvent event) { + mDragging = false; + mDragTask = null; + mTaskView = null; + mDragView = null; + mLastDockState = null; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java index 67e3f8271132..bc1f4814119e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java @@ -1296,7 +1296,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal RecentsTaskLoader.getInstance().loadTaskData(task); // If the doze trigger has already fired, then update the state for this task view - if (mUIDozeTrigger.hasTriggered()) { + if (mUIDozeTrigger.hasTriggered() || mConfig.multiWindowEnabled) { tv.setNoUserInteractionState(); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java index 910db873e0b9..f213a10bac73 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskView.java @@ -21,13 +21,17 @@ import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Outline; import android.graphics.Paint; +import android.graphics.Point; import android.graphics.PorterDuff; import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.util.AttributeSet; +import android.view.MotionEvent; import android.view.View; import android.view.ViewOutlineProvider; import android.view.animation.AccelerateInterpolator; @@ -36,17 +40,22 @@ import android.view.animation.Interpolator; import android.widget.FrameLayout; import com.android.systemui.R; import com.android.systemui.recents.Constants; +import com.android.systemui.recents.RecentsActivity; import com.android.systemui.recents.RecentsActivityLaunchState; import com.android.systemui.recents.RecentsConfiguration; import com.android.systemui.recents.events.EventBus; import com.android.systemui.recents.events.ui.DismissTaskEvent; +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; import com.android.systemui.recents.misc.Utilities; +import com.android.systemui.recents.model.RecentsTaskLoader; import com.android.systemui.recents.model.Task; import com.android.systemui.statusbar.phone.PhoneStatusBar; /* A task view */ public class TaskView extends FrameLayout implements Task.TaskCallbacks, - View.OnClickListener { + View.OnClickListener, View.OnLongClickListener { /** The TaskView callbacks */ interface TaskViewCallbacks { @@ -79,6 +88,8 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, View mActionButtonView; TaskViewCallbacks mCb; + Point mDownTouchPos = new Point(); + Interpolator mFastOutSlowInInterpolator; Interpolator mFastOutLinearInInterpolator; Interpolator mQuintOutInterpolator; @@ -169,6 +180,14 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, } @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + mDownTouchPos.set((int) (ev.getX() * getScaleX()), (int) (ev.getY() * getScaleY())); + } + return super.onInterceptTouchEvent(ev); + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); @@ -719,6 +738,7 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, mHeaderView.rebindToTask(mTask); // Rebind any listeners mActionButtonView.setOnClickListener(this); + setOnLongClickListener(mConfig.hasDockedTasks ? null : this); } mTaskDataLoaded = true; } @@ -753,4 +773,69 @@ public class TaskView extends FrameLayout implements Task.TaskCallbacks, mCb.onTaskViewClicked(this, mTask, (v == mActionButtonView)); } } + + /**** View.OnLongClickListener Implementation ****/ + + @Override + public boolean onLongClick(View v) { + if (v == this) { + // Start listening for drag events + setClipViewInStack(false); + + int width = (int) (getScaleX() * getWidth()); + int height = (int) (getScaleY() * 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); + + // Initiate the drag + final DragView dragView = new DragView(getContext(), dragBitmap, mDownTouchPos); + dragView.setOutlineProvider(mViewBounds); + dragView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { + @Override + public void onViewAttachedToWindow(View v) { + // Hide this task view after the drag view is attached + setVisibility(View.INVISIBLE); + // Animate the alpha slightly to indicate dragging + dragView.setElevation(getElevation()); + dragView.setTranslationZ(getTranslationZ()); + dragView.animate() + .alpha(0.75f) + .setDuration(175) + .setInterpolator(new AccelerateInterpolator(1.5f)) + .withLayer() + .start(); + } + + @Override + public void onViewDetachedFromWindow(View v) { + } + }); + EventBus.getDefault().register(this, RecentsActivity.EVENT_BUS_PRIORITY + 1); + EventBus.getDefault().send(new DragStartEvent(mTask, this, dragView)); + return true; + } + return false; + } + + /**** 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); + + // Show this task view + setVisibility(View.VISIBLE); + } + }); + EventBus.getDefault().unregister(this); + } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java index 7de8b7be5134..a71fdaab985e 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskViewHeader.java @@ -471,7 +471,7 @@ public class TaskViewHeader extends FrameLayout // In accessibility, a single click on the focused app info button will show it EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask)); } else if (v == mDismissButton) { - TaskView tv = (TaskView) getParent().getParent(); + TaskView tv = Utilities.findParent(this, TaskView.class); tv.dismissTask(); // Keep track of deletions by the dismiss button |