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
17package com.android.systemui.statusbar.phone;
18
19import android.content.Context;
20import android.view.MotionEvent;
21import android.view.ViewConfiguration;
22
23import com.android.systemui.Gefingerpoken;
24import com.android.systemui.statusbar.ExpandableNotificationRow;
25import com.android.systemui.statusbar.ExpandableView;
26import com.android.systemui.statusbar.NotificationData;
27import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
28
29/**
30 * A helper class to handle touches on the heads-up views.
31 */
32public class HeadsUpTouchHelper implements Gefingerpoken {
33
34    private HeadsUpManagerPhone mHeadsUpManager;
35    private NotificationStackScrollLayout mStackScroller;
36    private int mTrackingPointer;
37    private float mTouchSlop;
38    private float mInitialTouchX;
39    private float mInitialTouchY;
40    private boolean mTouchingHeadsUpView;
41    private boolean mTrackingHeadsUp;
42    private boolean mCollapseSnoozes;
43    private NotificationPanelView mPanel;
44    private ExpandableNotificationRow mPickedChild;
45
46    public HeadsUpTouchHelper(HeadsUpManagerPhone headsUpManager,
47            NotificationStackScrollLayout stackScroller,
48            NotificationPanelView notificationPanelView) {
49        mHeadsUpManager = headsUpManager;
50        mStackScroller = stackScroller;
51        mPanel = notificationPanelView;
52        Context context = stackScroller.getContext();
53        final ViewConfiguration configuration = ViewConfiguration.get(context);
54        mTouchSlop = configuration.getScaledTouchSlop();
55    }
56
57    public boolean isTrackingHeadsUp() {
58        return mTrackingHeadsUp;
59    }
60
61    @Override
62    public boolean onInterceptTouchEvent(MotionEvent event) {
63        if (!mTouchingHeadsUpView && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
64            return false;
65        }
66        int pointerIndex = event.findPointerIndex(mTrackingPointer);
67        if (pointerIndex < 0) {
68            pointerIndex = 0;
69            mTrackingPointer = event.getPointerId(pointerIndex);
70        }
71        final float x = event.getX(pointerIndex);
72        final float y = event.getY(pointerIndex);
73        switch (event.getActionMasked()) {
74            case MotionEvent.ACTION_DOWN:
75                mInitialTouchY = y;
76                mInitialTouchX = x;
77                setTrackingHeadsUp(false);
78                ExpandableView child = mStackScroller.getChildAtRawPosition(x, y);
79                mTouchingHeadsUpView = false;
80                if (child instanceof ExpandableNotificationRow) {
81                    mPickedChild = (ExpandableNotificationRow) child;
82                    mTouchingHeadsUpView = !mStackScroller.isExpanded()
83                            && mPickedChild.isHeadsUp() && mPickedChild.isPinned();
84                } else if (child == null && !mStackScroller.isExpanded()) {
85                    // We might touch above the visible heads up child, but then we still would
86                    // like to capture it.
87                    NotificationData.Entry topEntry = mHeadsUpManager.getTopEntry();
88                    if (topEntry != null && topEntry.row.isPinned()) {
89                        mPickedChild = topEntry.row;
90                        mTouchingHeadsUpView = true;
91                    }
92                }
93                break;
94            case MotionEvent.ACTION_POINTER_UP:
95                final int upPointer = event.getPointerId(event.getActionIndex());
96                if (mTrackingPointer == upPointer) {
97                    // gesture is ongoing, find a new pointer to track
98                    final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1;
99                    mTrackingPointer = event.getPointerId(newIndex);
100                    mInitialTouchX = event.getX(newIndex);
101                    mInitialTouchY = event.getY(newIndex);
102                }
103                break;
104
105            case MotionEvent.ACTION_MOVE:
106                final float h = y - mInitialTouchY;
107                if (mTouchingHeadsUpView && Math.abs(h) > mTouchSlop
108                        && Math.abs(h) > Math.abs(x - mInitialTouchX)) {
109                    setTrackingHeadsUp(true);
110                    mCollapseSnoozes = h < 0;
111                    mInitialTouchX = x;
112                    mInitialTouchY = y;
113                    int startHeight = (int) (mPickedChild.getActualHeight()
114                                                + mPickedChild.getTranslationY());
115                    mPanel.setPanelScrimMinFraction((float) startHeight
116                            / mPanel.getMaxPanelHeight());
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();
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
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