blob: 165eb1dda07762fd767cf6ecf82b73280efcebb7 [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();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080084 cleanUpDismissTarget();
Joshua Tsuji442b6272019-02-08 13:23:43 -050085 mTouchedView = null;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080086 return false;
87 }
88
Joshua Tsuji442b6272019-02-08 13:23:43 -050089 final boolean isStack = mStack.equals(mTouchedView);
90 final float rawX = event.getRawX();
91 final float rawY = event.getRawY();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080092
Joshua Tsuji442b6272019-02-08 13:23:43 -050093 // The coordinates of the touch event, in terms of the touched view's position.
94 final float viewX = mViewPositionOnTouchDown.x + rawX - mTouchDown.x;
95 final float viewY = mViewPositionOnTouchDown.y + rawY - mTouchDown.y;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -080096 switch (action) {
97 case MotionEvent.ACTION_DOWN:
98 trackMovement(event);
99
Mady Mellor2f01ed92018-11-07 09:24:04 -0800100 mDismissViewController.createDismissTarget();
101 mHandler.postDelayed(mShowDismissAffordance, SHOW_TARGET_DELAY);
102
Joshua Tsuji442b6272019-02-08 13:23:43 -0500103 mTouchDown.set(rawX, rawY);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800104
Joshua Tsuji442b6272019-02-08 13:23:43 -0500105 if (isStack) {
106 mViewPositionOnTouchDown.set(mStack.getStackPosition());
107 mStack.onDragStart();
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800108 } else {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500109 mViewPositionOnTouchDown.set(
110 mTouchedView.getTranslationX(), mTouchedView.getTranslationY());
111 mStack.onBubbleDragStart(mTouchedView);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800112 }
113
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800114 break;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800115 case MotionEvent.ACTION_MOVE:
116 trackMovement(event);
Joshua Tsuji442b6272019-02-08 13:23:43 -0500117 final float deltaX = rawX - mTouchDown.x;
118 final float deltaY = rawY - mTouchDown.y;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800119
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800120 if ((deltaX * deltaX) + (deltaY * deltaY) > mTouchSlopSquared && !mMovedEnough) {
121 mMovedEnough = true;
122 }
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800123
124 if (mMovedEnough) {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500125 if (isStack) {
126 mStack.onDragged(viewX, viewY);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800127 } else {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500128 mStack.onBubbleDragged(mTouchedView, viewX, viewY);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800129 }
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800130 }
Joshua Tsuji442b6272019-02-08 13:23:43 -0500131
Mady Mellor2f01ed92018-11-07 09:24:04 -0800132 // TODO - when we're in the target stick to it / animate in some way?
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800133 mInDismissTarget = mDismissViewController.updateTarget(
Joshua Tsuji442b6272019-02-08 13:23:43 -0500134 isStack ? mStack.getBubbleAt(0) : mTouchedView);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800135 break;
136
137 case MotionEvent.ACTION_CANCEL:
Joshua Tsuji442b6272019-02-08 13:23:43 -0500138 mTouchedView = null;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800139 cleanUpDismissTarget();
140 break;
141
142 case MotionEvent.ACTION_UP:
143 trackMovement(event);
Joshua Tsuji442b6272019-02-08 13:23:43 -0500144 if (mInDismissTarget && isStack) {
145 mController.dismissStack();
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800146 } else if (mMovedEnough) {
147 mVelocityTracker.computeCurrentVelocity(1000);
148 final float velX = mVelocityTracker.getXVelocity();
149 final float velY = mVelocityTracker.getYVelocity();
Joshua Tsuji442b6272019-02-08 13:23:43 -0500150 if (isStack) {
151 mStack.onDragFinish(viewX, viewY, velX, velY);
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800152 } else {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500153 final boolean dismissed = mInDismissTarget || velY > DISMISS_MIN_VELOCITY;
154 mStack.onBubbleDragFinish(
155 mTouchedView, viewX, viewY, velX, velY, /* dismissed */ dismissed);
156 if (dismissed) {
157 mController.removeBubble(((BubbleView) mTouchedView).getKey());
158 }
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800159 }
Joshua Tsuji442b6272019-02-08 13:23:43 -0500160 } else if (mTouchedView.equals(mStack.getExpandedBubbleView())) {
161 mStack.collapseStack();
162 } else if (isStack) {
163 if (mStack.isExpanded()) {
164 mStack.collapseStack();
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800165 } else {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500166 mStack.expandStack();
Mady Mellor3f2efdb2018-11-21 11:30:45 -0800167 }
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800168 } else {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500169 mStack.setExpandedBubble(((BubbleView) mTouchedView).getKey());
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800170 }
Joshua Tsuji442b6272019-02-08 13:23:43 -0500171
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800172 cleanUpDismissTarget();
173 mVelocityTracker.recycle();
174 mVelocityTracker = null;
Joshua Tsuji442b6272019-02-08 13:23:43 -0500175 mTouchedView = null;
176 mMovedEnough = false;
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800177 break;
178 }
Joshua Tsuji442b6272019-02-08 13:23:43 -0500179
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800180 return true;
181 }
182
183 /**
184 * Removes the dismiss target and cancels any pending callbacks to show it.
185 */
186 private void cleanUpDismissTarget() {
187 mHandler.removeCallbacks(mShowDismissAffordance);
188 mDismissViewController.destroyDismissTarget();
189 }
190
Mady Mellorc3d6f7d2018-11-07 09:36:56 -0800191
192 private void trackMovement(MotionEvent event) {
193 if (mVelocityTracker == null) {
194 mVelocityTracker = VelocityTracker.obtain();
195 }
196 mVelocityTracker.addMovement(event);
197 }
198}