blob: a7170d0256e39032d1af85d121d60c166d556887 [file] [log] [blame]
Mady Mellorc3d6f7d2018-11-07 09:36:56 -08001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.bubbles;
18
Mady Mellor2f01ed92018-11-07 09:24:04 -080019import static com.android.systemui.pip.phone.PipDismissViewController.SHOW_TARGET_DELAY;
20
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080021import android.content.Context;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080022import android.graphics.PointF;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080023import android.os.Handler;
24import android.view.MotionEvent;
25import android.view.VelocityTracker;
26import android.view.View;
27import android.view.ViewConfiguration;
28
29import com.android.systemui.Dependency;
30import com.android.systemui.pip.phone.PipDismissViewController;
31
32/**
33 * Handles interpreting touches on a {@link BubbleStackView}. This includes expanding, collapsing,
34 * dismissing, and flings.
35 */
Mark Renouf89b1a4a2018-12-04 14:59:45 -050036class BubbleTouchHandler implements View.OnTouchListener {
Joshua Tsuji442b6272019-02-08 13:23:43 -050037 /** Velocity required to dismiss a bubble without dragging it into the dismiss target. */
38 private static final float DISMISS_MIN_VELOCITY = 4000f;
39
40 private final PointF mTouchDown = new PointF();
41 private final PointF mViewPositionOnTouchDown = new PointF();
42 private final BubbleStackView mStack;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080043
44 private BubbleController mController = Dependency.get(BubbleController.class);
45 private PipDismissViewController mDismissViewController;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080046
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080047 private boolean mMovedEnough;
48 private int mTouchSlopSquared;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080049 private VelocityTracker mVelocityTracker;
50
51 private boolean mInDismissTarget;
52 private Handler mHandler = new Handler();
53 private Runnable mShowDismissAffordance = new Runnable() {
54 @Override
55 public void run() {
56 mDismissViewController.showDismissTarget();
57 }
58 };
59
Joshua Tsuji442b6272019-02-08 13:23:43 -050060 /** View that was initially touched, when we received the first ACTION_DOWN event. */
61 private View mTouchedView;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080062
Joshua Tsuji442b6272019-02-08 13:23:43 -050063 BubbleTouchHandler(Context context, BubbleStackView stackView) {
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080064 final int touchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
65 mTouchSlopSquared = touchSlop * touchSlop;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080066 mDismissViewController = new PipDismissViewController(context);
Joshua Tsuji442b6272019-02-08 13:23:43 -050067 mStack = stackView;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080068 }
69
70 @Override
71 public boolean onTouch(View v, MotionEvent event) {
Joshua Tsuji442b6272019-02-08 13:23:43 -050072 final int action = event.getActionMasked();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080073
Joshua Tsuji442b6272019-02-08 13:23:43 -050074 // If we aren't currently in the process of touching a view, figure out what we're touching.
75 // It'll be the stack, an individual bubble, or nothing.
76 if (mTouchedView == null) {
77 mTouchedView = mStack.getTargetView(event);
78 }
79
80 // If this is an ACTION_OUTSIDE event, or the stack reported that we aren't touching
81 // anything, collapse the stack.
82 if (action == MotionEvent.ACTION_OUTSIDE || mTouchedView == null) {
83 mStack.collapseStack();
Mark Renoufa867b7e2019-02-28 15:14:47 -050084 resetForNextGesture();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080085 return false;
86 }
87
Joshua Tsuji442b6272019-02-08 13:23:43 -050088 final boolean isStack = mStack.equals(mTouchedView);
89 final float rawX = event.getRawX();
90 final float rawY = event.getRawY();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080091
Joshua Tsuji442b6272019-02-08 13:23:43 -050092 // The coordinates of the touch event, in terms of the touched view's position.
93 final float viewX = mViewPositionOnTouchDown.x + rawX - mTouchDown.x;
94 final float viewY = mViewPositionOnTouchDown.y + rawY - mTouchDown.y;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080095 switch (action) {
96 case MotionEvent.ACTION_DOWN:
97 trackMovement(event);
98
Mady Mellor2f01ed92018-11-07 09:24:04 -080099 mDismissViewController.createDismissTarget();
100 mHandler.postDelayed(mShowDismissAffordance, SHOW_TARGET_DELAY);
101
Joshua Tsuji442b6272019-02-08 13:23:43 -0500102 mTouchDown.set(rawX, rawY);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800103
Joshua Tsuji442b6272019-02-08 13:23:43 -0500104 if (isStack) {
105 mViewPositionOnTouchDown.set(mStack.getStackPosition());
106 mStack.onDragStart();
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800107 } else {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500108 mViewPositionOnTouchDown.set(
109 mTouchedView.getTranslationX(), mTouchedView.getTranslationY());
110 mStack.onBubbleDragStart(mTouchedView);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800111 }
112
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800113 break;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800114 case MotionEvent.ACTION_MOVE:
115 trackMovement(event);
Joshua Tsuji442b6272019-02-08 13:23:43 -0500116 final float deltaX = rawX - mTouchDown.x;
117 final float deltaY = rawY - mTouchDown.y;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800118
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800119 if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) {
120 mMovedEnough = true;
121 }
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800122
123 if (mMovedEnough) {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500124 if (isStack) {
125 mStack.onDragged(viewX, viewY);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800126 } else {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500127 mStack.onBubbleDragged(mTouchedView, viewX, viewY);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800128 }
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800129 }
Joshua Tsuji442b6272019-02-08 13:23:43 -0500130
Mady Mellor2f01ed92018-11-07 09:24:04 -0800131 // TODO - when we're in the target stick to it / animate in some way?
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800132 mInDismissTarget = mDismissViewController.updateTarget(
Joshua Tsuji442b6272019-02-08 13:23:43 -0500133 isStack ? mStack.getBubbleAt(0) : mTouchedView);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800134 break;
135
136 case MotionEvent.ACTION_CANCEL:
Mark Renoufa867b7e2019-02-28 15:14:47 -0500137 resetForNextGesture();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800138 break;
139
140 case MotionEvent.ACTION_UP:
141 trackMovement(event);
Joshua Tsuji442b6272019-02-08 13:23:43 -0500142 if (mInDismissTarget && isStack) {
Mark Renouf08bc42a2019-03-07 13:01:59 -0500143 mController.dismissStack(BubbleController.DISMISS_USER_GESTURE);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800144 } else if (mMovedEnough) {
Mark Renoufa867b7e2019-02-28 15:14:47 -0500145 mVelocityTracker.computeCurrentVelocity(/* maxVelocity */ 1000);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800146 final float velX = mVelocityTracker.getXVelocity();
147 final float velY = mVelocityTracker.getYVelocity();
Joshua Tsuji442b6272019-02-08 13:23:43 -0500148 if (isStack) {
149 mStack.onDragFinish(viewX, viewY, velX, velY);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800150 } else {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500151 final boolean dismissed = mInDismissTarget || velY > DISMISS_MIN_VELOCITY;
152 mStack.onBubbleDragFinish(
153 mTouchedView, viewX, viewY, velX, velY, /* dismissed */ dismissed);
154 if (dismissed) {
Mark Renouf08bc42a2019-03-07 13:01:59 -0500155 mController.removeBubble(((BubbleView) mTouchedView).getKey(),
156 BubbleController.DISMISS_USER_GESTURE);
Joshua Tsuji442b6272019-02-08 13:23:43 -0500157 }
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800158 }
Mark Renoufa867b7e2019-02-28 15:14:47 -0500159 } else if (mTouchedView == mStack.getExpandedBubbleView()) {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500160 mStack.collapseStack();
161 } else if (isStack) {
162 if (mStack.isExpanded()) {
163 mStack.collapseStack();
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800164 } else {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500165 mStack.expandStack();
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800166 }
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800167 } else {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500168 mStack.setExpandedBubble(((BubbleView) mTouchedView).getKey());
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800169 }
Joshua Tsuji442b6272019-02-08 13:23:43 -0500170
Mark Renoufa867b7e2019-02-28 15:14:47 -0500171 resetForNextGesture();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800172 break;
173 }
Joshua Tsuji442b6272019-02-08 13:23:43 -0500174
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800175 return true;
176 }
177
Mark Renoufa867b7e2019-02-28 15:14:47 -0500178 /** Clears all touch-related state. */
179 private void resetForNextGesture() {
180 cleanUpDismissTarget();
181 if (mVelocityTracker != null) {
182 mVelocityTracker.recycle();
183 mVelocityTracker = null;
184 }
185 mTouchedView = null;
186 mMovedEnough = false;
187 mInDismissTarget = false;
188 }
189
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800190 /**
191 * Removes the dismiss target and cancels any pending callbacks to show it.
192 */
193 private void cleanUpDismissTarget() {
194 mHandler.removeCallbacks(mShowDismissAffordance);
195 mDismissViewController.destroyDismissTarget();
196 }
197
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800198
199 private void trackMovement(MotionEvent event) {
200 if (mVelocityTracker == null) {
201 mVelocityTracker = VelocityTracker.obtain();
202 }
203 mVelocityTracker.addMovement(event);
204 }
205}