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 HeadsUpManagerPhone mHeadsUpManager;
34     private Callback mCallback;
35     private int mTrackingPointer;
36     private float mTouchSlop;
37     private float mInitialTouchX;
38     private float mInitialTouchY;
39     private boolean mTouchingHeadsUpView;
40     private boolean mTrackingHeadsUp;
41     private boolean mCollapseSnoozes;
42     private NotificationPanelViewController mPanel;
43     private ExpandableNotificationRow mPickedChild;
44 
HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager, Callback callback, NotificationPanelViewController notificationPanelView)45     public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
46             Callback callback,
47             NotificationPanelViewController 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                     float maxPanelHeight = mPanel.getMaxPanelHeight();
118                     mPanel.setPanelScrimMinFraction(maxPanelHeight > 0f
119                             ? (float) startHeight / maxPanelHeight : 0f);
120                     mPanel.startExpandMotion(x, y, true /* startTracking */, startHeight);
121                     // This call needs to be after the expansion start otherwise we will get a
122                     // flicker of one frame as it's not expanded yet.
123                     mHeadsUpManager.unpinAll(true);
124                     mPanel.clearNotificationEffects();
125                     endMotion();
126                     return true;
127                 }
128                 break;
129 
130             case MotionEvent.ACTION_CANCEL:
131             case MotionEvent.ACTION_UP:
132                 if (mPickedChild != null && mTouchingHeadsUpView) {
133                     // We may swallow this click if the heads up just came in.
134                     if (mHeadsUpManager.shouldSwallowClick(
135                             mPickedChild.getEntry().getSbn().getKey())) {
136                         endMotion();
137                         return true;
138                     }
139                 }
140                 endMotion();
141                 break;
142         }
143         return false;
144     }
145 
setTrackingHeadsUp(boolean tracking)146     private void setTrackingHeadsUp(boolean tracking) {
147         mTrackingHeadsUp = tracking;
148         mHeadsUpManager.setTrackingHeadsUp(tracking);
149         mPanel.setTrackedHeadsUp(tracking ? mPickedChild : null);
150     }
151 
notifyFling(boolean collapse)152     public void notifyFling(boolean collapse) {
153         if (collapse && mCollapseSnoozes) {
154             mHeadsUpManager.snooze();
155         }
156         mCollapseSnoozes = false;
157     }
158 
159     @Override
onTouchEvent(MotionEvent event)160     public boolean onTouchEvent(MotionEvent event) {
161         if (!mTrackingHeadsUp) {
162             return false;
163         }
164         switch (event.getActionMasked()) {
165             case MotionEvent.ACTION_UP:
166             case MotionEvent.ACTION_CANCEL:
167                 endMotion();
168                 setTrackingHeadsUp(false);
169                 break;
170         }
171         return true;
172     }
173 
endMotion()174     private void endMotion() {
175         mTrackingPointer = -1;
176         mPickedChild = null;
177         mTouchingHeadsUpView = false;
178     }
179 
180     public interface Callback {
getChildAtRawPosition(float touchX, float touchY)181         ExpandableView getChildAtRawPosition(float touchX, float touchY);
isExpanded()182         boolean isExpanded();
getContext()183         Context getContext();
184     }
185 }
186