1 /*
2  * Copyright (C) 2015 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 
17 package com.android.systemui.statusbar.phone;
18 
19 import android.content.Context;
20 import android.view.MotionEvent;
21 import android.view.ViewConfiguration;
22 
23 import com.android.systemui.Gefingerpoken;
24 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
25 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
26 import com.android.systemui.statusbar.notification.row.ExpandableView;
27 
28 /**
29  * A helper class to handle touches on the heads-up views.
30  */
31 public class HeadsUpTouchHelper implements Gefingerpoken {
32 
33     private final HeadsUpManagerPhone mHeadsUpManager;
34     private final Callback mCallback;
35     private int mTrackingPointer;
36     private final float mTouchSlop;
37     private float mInitialTouchX;
38     private float mInitialTouchY;
39     private boolean mTouchingHeadsUpView;
40     private boolean mTrackingHeadsUp;
41     private boolean mCollapseSnoozes;
42     private final HeadsUpNotificationViewController mPanel;
43     private ExpandableNotificationRow mPickedChild;
44 
HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager, Callback callback, HeadsUpNotificationViewController notificationPanelView)45     public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
46             Callback callback,
47             HeadsUpNotificationViewController notificationPanelView) {
48         mHeadsUpManager = headsUpManager;
49         mCallback = callback;
50         mPanel = notificationPanelView;
51         Context context = mCallback.getContext();
52         final ViewConfiguration configuration = ViewConfiguration.get(context);
53         mTouchSlop = configuration.getScaledTouchSlop();
54     }
55 
isTrackingHeadsUp()56     public boolean isTrackingHeadsUp() {
57         return mTrackingHeadsUp;
58     }
59 
60     @Override
onInterceptTouchEvent(MotionEvent event)61     public boolean onInterceptTouchEvent(MotionEvent event) {
62         if (!mTouchingHeadsUpView && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
63             return false;
64         }
65         int pointerIndex = event.findPointerIndex(mTrackingPointer);
66         if (pointerIndex < 0) {
67             pointerIndex = 0;
68             mTrackingPointer = event.getPointerId(pointerIndex);
69         }
70         final float x = event.getX(pointerIndex);
71         final float y = event.getY(pointerIndex);
72         switch (event.getActionMasked()) {
73             case MotionEvent.ACTION_DOWN:
74                 mInitialTouchY = y;
75                 mInitialTouchX = x;
76                 setTrackingHeadsUp(false);
77                 ExpandableView child = mCallback.getChildAtRawPosition(x, y);
78                 mTouchingHeadsUpView = false;
79                 if (child instanceof ExpandableNotificationRow) {
80                     ExpandableNotificationRow pickedChild = (ExpandableNotificationRow) child;
81                     mTouchingHeadsUpView = !mCallback.isExpanded()
82                             && pickedChild.isHeadsUp() && pickedChild.isPinned();
83                     if (mTouchingHeadsUpView) {
84                         mPickedChild = pickedChild;
85                     }
86                 } else if (child == null && !mCallback.isExpanded()) {
87                     // We might touch above the visible heads up child, but then we still would
88                     // like to capture it.
89                     NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
90                     if (topEntry != null && topEntry.isRowPinned()) {
91                         mPickedChild = topEntry.getRow();
92                         mTouchingHeadsUpView = true;
93                     }
94                 }
95                 break;
96             case MotionEvent.ACTION_POINTER_UP:
97                 final int upPointer = event.getPointerId(event.getActionIndex());
98                 if (mTrackingPointer == upPointer) {
99                     // gesture is ongoing, find a new pointer to track
100                     final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
101                     mTrackingPointer = event.getPointerId(newIndex);
102                     mInitialTouchX = event.getX(newIndex);
103                     mInitialTouchY = event.getY(newIndex);
104                 }
105                 break;
106 
107             case MotionEvent.ACTION_MOVE:
108                 final float h = y - mInitialTouchY;
109                 if (mTouchingHeadsUpView && Math.abs(h) > mTouchSlop
110                         && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
111                     setTrackingHeadsUp(true);
112                     mCollapseSnoozes = h < 0;
113                     mInitialTouchX = x;
114                     mInitialTouchY = y;
115                     int startHeight = (int) (mPickedChild.getActualHeight()
116                                                 + mPickedChild.getTranslationY());
117                     mPanel.setHeadsUpDraggingStartingHeight(startHeight);
118                     mPanel.startExpand(x, y, true /* startTracking */, startHeight);
119                     // This call needs to be after the expansion start otherwise we will get a
120                     // flicker of one frame as it's not expanded yet.
121                     mHeadsUpManager.unpinAll(true);
122                     mPanel.clearNotificationEffects();
123                     endMotion();
124                     return true;
125                 }
126                 break;
127 
128             case MotionEvent.ACTION_CANCEL:
129             case MotionEvent.ACTION_UP:
130                 if (mPickedChild != null && mTouchingHeadsUpView) {
131                     // We may swallow this click if the heads up just came in.
132                     if (mHeadsUpManager.shouldSwallowClick(
133                             mPickedChild.getEntry().getSbn().getKey())) {
134                         endMotion();
135                         return true;
136                     }
137                 }
138                 endMotion();
139                 break;
140         }
141         return false;
142     }
143 
144     private void setTrackingHeadsUp(boolean tracking) {
145         mTrackingHeadsUp = tracking;
146         mHeadsUpManager.setTrackingHeadsUp(tracking);
147         mPanel.setTrackedHeadsUp(tracking ? mPickedChild : null);
148     }
149 
150     public void notifyFling(boolean collapse) {
151         if (collapse && mCollapseSnoozes) {
152             mHeadsUpManager.snooze();
153         }
154         mCollapseSnoozes = false;
155     }
156 
157     @Override
158     public boolean onTouchEvent(MotionEvent event) {
159         if (!mTrackingHeadsUp) {
160             return false;
161         }
162         switch (event.getActionMasked()) {
163             case MotionEvent.ACTION_UP:
164             case MotionEvent.ACTION_CANCEL:
165                 endMotion();
166                 setTrackingHeadsUp(false);
167                 break;
168         }
169         return true;
170     }
171 
172     private void endMotion() {
173         mTrackingPointer = -1;
174         mPickedChild = null;
175         mTouchingHeadsUpView = false;
176     }
177 
178     public interface Callback {
179         ExpandableView getChildAtRawPosition(float touchX, float touchY);
180         boolean isExpanded();
181         Context getContext();
182     }
183 
184     /** The controller for a view that houses heads up notifications. */
185     public interface HeadsUpNotificationViewController {
186         /** Called when a HUN is dragged to indicate the starting height for shade motion. */
187         void setHeadsUpDraggingStartingHeight(int startHeight);
188 
189         /** Sets notification that is being expanded. */
190         void setTrackedHeadsUp(ExpandableNotificationRow expandableNotificationRow);
191 
192         /** Called when a MotionEvent is about to trigger expansion. */
193         void startExpand(float newX, float newY, boolean startTracking, float expandedHeight);
194 
195         /** Clear any effects that were added for the expansion. */
196         void clearNotificationEffects();
197     }
198 }
199