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 NotificationPanelView mPanel;
43     private ExpandableNotificationRow mPickedChild;
44 
HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager, Callback callback, NotificationPanelView notificationPanelView)45     public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
46             Callback callback,
47             NotificationPanelView 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                     mPickedChild = (ExpandableNotificationRow) child;
81                     mTouchingHeadsUpView = !mCallback.isExpanded()
82                             && mPickedChild.isHeadsUp() && mPickedChild.isPinned();
83                 } else if (child == null && !mCallback.isExpanded()) {
84                     // We might touch above the visible heads up child, but then we still would
85                     // like to capture it.
86                     NotificationEntry topEntry = mHeadsUpManager.getTopEntry();
87                     if (topEntry != null && topEntry.isRowPinned()) {
88                         mPickedChild = topEntry.getRow();
89                         mTouchingHeadsUpView = true;
90                     }
91                 }
92                 break;
93             case MotionEvent.ACTION_POINTER_UP:
94                 final int upPointer = event.getPointerId(event.getActionIndex());
95                 if (mTrackingPointer == upPointer) {
96                     // gesture is ongoing, find a new pointer to track
97                     final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
98                     mTrackingPointer = event.getPointerId(newIndex);
99                     mInitialTouchX = event.getX(newIndex);
100                     mInitialTouchY = event.getY(newIndex);
101                 }
102                 break;
103 
104             case MotionEvent.ACTION_MOVE:
105                 final float h = y - mInitialTouchY;
106                 if (mTouchingHeadsUpView && Math.abs(h) > mTouchSlop
107                         && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
108                     setTrackingHeadsUp(true);
109                     mCollapseSnoozes = h < 0;
110                     mInitialTouchX = x;
111                     mInitialTouchY = y;
112                     int startHeight = (int) (mPickedChild.getActualHeight()
113                                                 + mPickedChild.getTranslationY());
114                     float maxPanelHeight = mPanel.getMaxPanelHeight();
115                     mPanel.setPanelScrimMinFraction(maxPanelHeight > 0f
116                             ? (float) startHeight / maxPanelHeight : 0f);
117                     mPanel.startExpandMotion(x, y, true /* startTracking */, startHeight);
118                     mPanel.startExpandingFromPeek();
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.getStatusBarNotification().getKey())) {
134                         endMotion();
135                         return true;
136                     }
137                 }
138                 endMotion();
139                 break;
140         }
141         return false;
142     }
143 
setTrackingHeadsUp(boolean tracking)144     private void setTrackingHeadsUp(boolean tracking) {
145         mTrackingHeadsUp = tracking;
146         mHeadsUpManager.setTrackingHeadsUp(tracking);
147         mPanel.setTrackedHeadsUp(tracking ? mPickedChild : null);
148     }
149 
notifyFling(boolean collapse)150     public void notifyFling(boolean collapse) {
151         if (collapse && mCollapseSnoozes) {
152             mHeadsUpManager.snooze();
153         }
154         mCollapseSnoozes = false;
155     }
156 
157     @Override
onTouchEvent(MotionEvent event)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 
endMotion()172     private void endMotion() {
173         mTrackingPointer = -1;
174         mPickedChild = null;
175         mTouchingHeadsUpView = false;
176     }
177 
178     public interface Callback {
getChildAtRawPosition(float touchX, float touchY)179         ExpandableView getChildAtRawPosition(float touchX, float touchY);
isExpanded()180         boolean isExpanded();
getContext()181         Context getContext();
182     }
183 }
184