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.R;
25 import com.android.systemui.statusbar.ExpandableNotificationRow;
26 import com.android.systemui.statusbar.ExpandableView;
27 import com.android.systemui.statusbar.policy.HeadsUpManager;
28 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
29 
30 /**
31  * A helper class to handle touches on the heads-up views.
32  */
33 public class HeadsUpTouchHelper implements Gefingerpoken {
34 
35     private HeadsUpManager mHeadsUpManager;
36     private NotificationStackScrollLayout mStackScroller;
37     private int mTrackingPointer;
38     private float mTouchSlop;
39     private float mInitialTouchX;
40     private float mInitialTouchY;
41     private boolean mTouchingHeadsUpView;
42     private boolean mTrackingHeadsUp;
43     private boolean mCollapseSnoozes;
44     private NotificationPanelView mPanel;
45     private ExpandableNotificationRow mPickedChild;
46     private final int mNotificationsTopPadding;
47 
HeadsUpTouchHelper(HeadsUpManager headsUpManager, NotificationStackScrollLayout stackScroller, NotificationPanelView notificationPanelView)48     public HeadsUpTouchHelper(HeadsUpManager headsUpManager,
49             NotificationStackScrollLayout stackScroller,
50             NotificationPanelView notificationPanelView) {
51         mHeadsUpManager = headsUpManager;
52         mStackScroller = stackScroller;
53         mPanel = notificationPanelView;
54         Context context = stackScroller.getContext();
55         final ViewConfiguration configuration = ViewConfiguration.get(context);
56         mTouchSlop = configuration.getScaledTouchSlop();
57         mNotificationsTopPadding = context.getResources()
58                 .getDimensionPixelSize(R.dimen.notifications_top_padding);
59     }
60 
isTrackingHeadsUp()61     public boolean isTrackingHeadsUp() {
62         return mTrackingHeadsUp;
63     }
64 
65     @Override
onInterceptTouchEvent(MotionEvent event)66     public boolean onInterceptTouchEvent(MotionEvent event) {
67         if (!mTouchingHeadsUpView && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
68             return false;
69         }
70         int pointerIndex = event.findPointerIndex(mTrackingPointer);
71         if (pointerIndex < 0) {
72             pointerIndex = 0;
73             mTrackingPointer = event.getPointerId(pointerIndex);
74         }
75         final float x = event.getX(pointerIndex);
76         final float y = event.getY(pointerIndex);
77         switch (event.getActionMasked()) {
78             case MotionEvent.ACTION_DOWN:
79                 mInitialTouchY = y;
80                 mInitialTouchX = x;
81                 setTrackingHeadsUp(false);
82                 ExpandableView child = mStackScroller.getChildAtRawPosition(x, y);
83                 if (child == null && y < mNotificationsTopPadding) {
84                     // We should also allow drags from the margin above the heads up
85                     child = mStackScroller.getChildAtRawPosition(x, y + mNotificationsTopPadding);
86                 }
87                 mTouchingHeadsUpView = false;
88                 if (child instanceof ExpandableNotificationRow) {
89                     mPickedChild = (ExpandableNotificationRow) child;
90                     mTouchingHeadsUpView = mPickedChild.isHeadsUp() && mPickedChild.isPinned();
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 expandedHeight = mPickedChild.getActualHeight();
113                     mPanel.setPanelScrimMinFraction((float) expandedHeight
114                             / mPanel.getMaxPanelHeight());
115                     mPanel.startExpandMotion(x, y, true /* startTracking */, expandedHeight
116                             + mNotificationsTopPadding);
117                     mPanel.clearNotificattonEffects();
118                     mHeadsUpManager.unpinAll();
119                     return true;
120                 }
121                 break;
122 
123             case MotionEvent.ACTION_CANCEL:
124             case MotionEvent.ACTION_UP:
125                 if (mPickedChild != null && mTouchingHeadsUpView) {
126                     // We may swallow this click if the heads up just came in.
127                     if (mHeadsUpManager.shouldSwallowClick(
128                             mPickedChild.getStatusBarNotification().getKey())) {
129                         endMotion();
130                         return true;
131                     }
132                 }
133                 endMotion();
134                 break;
135         }
136         return false;
137     }
138 
139     private void setTrackingHeadsUp(boolean tracking) {
140         mTrackingHeadsUp = tracking;
141         mHeadsUpManager.setTrackingHeadsUp(tracking);
142         mPanel.setTrackingHeadsUp(tracking);
143     }
144 
145     public void notifyFling(boolean collapse) {
146         if (collapse && mCollapseSnoozes) {
147             mHeadsUpManager.snooze();
148         }
149         mCollapseSnoozes = false;
150     }
151 
152     @Override
153     public boolean onTouchEvent(MotionEvent event) {
154         if (!mTrackingHeadsUp) {
155             return false;
156         }
157         switch (event.getActionMasked()) {
158             case MotionEvent.ACTION_UP:
159             case MotionEvent.ACTION_CANCEL:
160                 endMotion();
161                 setTrackingHeadsUp(false);
162                 break;
163         }
164         return true;
165     }
166 
167     private void endMotion() {
168         mTrackingPointer = -1;
169         mPickedChild = null;
170         mTouchingHeadsUpView = false;
171     }
172 }
173