HeadsUpNotificationView.java revision 9bd9d0c96a37c0973f74809961a0f9508f65324e
1/*
2 * Copyright (C) 2011 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.policy;
18
19import android.content.Context;
20import android.content.res.Configuration;
21import android.graphics.Outline;
22import android.graphics.Rect;
23import android.util.AttributeSet;
24import android.util.Log;
25import android.view.MotionEvent;
26import android.view.View;
27import android.view.ViewConfiguration;
28import android.view.ViewGroup;
29import android.view.ViewOutlineProvider;
30import android.view.ViewTreeObserver;
31import android.view.accessibility.AccessibilityEvent;
32import android.widget.FrameLayout;
33
34import com.android.systemui.ExpandHelper;
35import com.android.systemui.Gefingerpoken;
36import com.android.systemui.R;
37import com.android.systemui.SwipeHelper;
38import com.android.systemui.statusbar.ExpandableView;
39import com.android.systemui.statusbar.NotificationData;
40import com.android.systemui.statusbar.phone.PhoneStatusBar;
41
42public class HeadsUpNotificationView extends FrameLayout implements SwipeHelper.Callback, ExpandHelper.Callback,
43        ViewTreeObserver.OnComputeInternalInsetsListener {
44    private static final String TAG = "HeadsUpNotificationView";
45    private static final boolean DEBUG = false;
46    private static final boolean SPEW = DEBUG;
47
48    Rect mTmpRect = new Rect();
49    int[] mTmpTwoArray = new int[2];
50
51    private final int mTouchSensitivityDelay;
52    private final float mMaxAlpha = 1f;
53    private SwipeHelper mSwipeHelper;
54    private EdgeSwipeHelper mEdgeSwipeHelper;
55
56    private PhoneStatusBar mBar;
57
58    private long mStartTouchTime;
59    private ViewGroup mContentHolder;
60
61    private NotificationData.Entry mHeadsUp;
62
63    public HeadsUpNotificationView(Context context, AttributeSet attrs) {
64        this(context, attrs, 0);
65    }
66
67    public HeadsUpNotificationView(Context context, AttributeSet attrs, int defStyle) {
68        super(context, attrs, defStyle);
69        mTouchSensitivityDelay = getResources().getInteger(R.integer.heads_up_sensitivity_delay);
70        if (DEBUG) Log.v(TAG, "create() " + mTouchSensitivityDelay);
71    }
72
73    public void updateResources() {
74        if (mContentHolder != null) {
75            final LayoutParams lp = (LayoutParams) mContentHolder.getLayoutParams();
76            lp.width = getResources().getDimensionPixelSize(R.dimen.notification_panel_width);
77            lp.gravity = getResources().getInteger(R.integer.notification_panel_layout_gravity);
78            mContentHolder.setLayoutParams(lp);
79        }
80    }
81
82    public void setBar(PhoneStatusBar bar) {
83        mBar = bar;
84    }
85
86    public ViewGroup getHolder() {
87        return mContentHolder;
88    }
89
90    public boolean showNotification(NotificationData.Entry headsUp) {
91        if (mHeadsUp != null && headsUp != null && !mHeadsUp.key.equals(headsUp.key)) {
92            // bump any previous heads up back to the shade
93            release();
94        }
95
96        mHeadsUp = headsUp;
97        if (mContentHolder != null) {
98            mContentHolder.removeAllViews();
99        }
100
101        if (mHeadsUp != null) {
102            mHeadsUp.row.setSystemExpanded(true);
103            mHeadsUp.row.setSensitive(false);
104            mHeadsUp.row.setHeadsUp(true);
105            mHeadsUp.row.setHideSensitive(
106                    false, false /* animated */, 0 /* delay */, 0 /* duration */);
107            if (mContentHolder == null) {
108                // too soon!
109                return false;
110            }
111            mContentHolder.setX(0);
112            mContentHolder.setVisibility(View.VISIBLE);
113            mContentHolder.setAlpha(mMaxAlpha);
114            mContentHolder.addView(mHeadsUp.row);
115            sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
116
117            mSwipeHelper.snapChild(mContentHolder, 1f);
118            mStartTouchTime = System.currentTimeMillis() + mTouchSensitivityDelay;
119
120            mHeadsUp.setInterruption();
121
122            // 2. Animate mHeadsUpNotificationView in
123            mBar.scheduleHeadsUpOpen();
124
125            // 3. Set alarm to age the notification off
126            mBar.resetHeadsUpDecayTimer();
127        }
128        return true;
129    }
130
131    @Override
132    protected void onVisibilityChanged(View changedView, int visibility) {
133        super.onVisibilityChanged(changedView, visibility);
134        if (changedView.getVisibility() == VISIBLE) {
135            sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
136        }
137    }
138
139    public boolean isShowing(String key) {
140        return mHeadsUp != null && mHeadsUp.key.equals(key);
141    }
142
143    /** Discard the Heads Up notification. */
144    public void clear() {
145        mHeadsUp = null;
146        mBar.scheduleHeadsUpClose();
147    }
148
149    /** Respond to dismissal of the Heads Up window. */
150    public void dismiss() {
151        if (mHeadsUp == null) return;
152        if (mHeadsUp.notification.isClearable()) {
153            mBar.onNotificationClear(mHeadsUp.notification);
154        } else {
155            release();
156        }
157        mHeadsUp = null;
158        mBar.scheduleHeadsUpClose();
159    }
160
161    /** Push any current Heads Up notification down into the shade. */
162    public void release() {
163        if (mHeadsUp != null) {
164            mBar.displayNotificationFromHeadsUp(mHeadsUp.notification);
165        }
166        mHeadsUp = null;
167    }
168
169    public void releaseAndClose() {
170        release();
171        mBar.scheduleHeadsUpClose();
172    }
173
174    public NotificationData.Entry getEntry() {
175        return mHeadsUp;
176    }
177
178    public boolean isClearable() {
179        return mHeadsUp == null || mHeadsUp.notification.isClearable();
180    }
181
182    // ViewGroup methods
183
184    private static final ViewOutlineProvider CONTENT_HOLDER_OUTLINE_PROVIDER =
185            new ViewOutlineProvider() {
186        @Override
187        public void getOutline(View view, Outline outline) {
188            int outlineLeft = view.getPaddingLeft();
189            int outlineTop = view.getPaddingTop();
190
191            // Apply padding to shadow.
192            outline.setRect(outlineLeft, outlineTop,
193                    view.getWidth() - outlineLeft - view.getPaddingRight(),
194                    view.getHeight() - outlineTop - view.getPaddingBottom());
195        }
196    };
197
198    @Override
199    public void onAttachedToWindow() {
200        final ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
201        float touchSlop = viewConfiguration.getScaledTouchSlop();
202        mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, getContext());
203        mSwipeHelper.setMaxSwipeProgress(mMaxAlpha);
204        mEdgeSwipeHelper = new EdgeSwipeHelper(touchSlop);
205
206        int minHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
207        int maxHeight = getResources().getDimensionPixelSize(R.dimen.notification_max_height);
208
209        mContentHolder = (ViewGroup) findViewById(R.id.content_holder);
210        mContentHolder.setOutlineProvider(CONTENT_HOLDER_OUTLINE_PROVIDER);
211
212        if (mHeadsUp != null) {
213            // whoops, we're on already!
214            showNotification(mHeadsUp);
215        }
216
217        getViewTreeObserver().addOnComputeInternalInsetsListener(this);
218    }
219
220    @Override
221    public boolean onInterceptTouchEvent(MotionEvent ev) {
222        if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
223        if (System.currentTimeMillis() < mStartTouchTime) {
224            return true;
225        }
226        return mEdgeSwipeHelper.onInterceptTouchEvent(ev)
227                || mSwipeHelper.onInterceptTouchEvent(ev)
228                || super.onInterceptTouchEvent(ev);
229    }
230
231    // View methods
232
233    @Override
234    public void onDraw(android.graphics.Canvas c) {
235        super.onDraw(c);
236        if (DEBUG) {
237            //Log.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: "
238            //        + getMeasuredHeight() + "px");
239            c.save();
240            c.clipRect(6, 6, c.getWidth() - 6, getMeasuredHeight() - 6,
241                    android.graphics.Region.Op.DIFFERENCE);
242            c.drawColor(0xFFcc00cc);
243            c.restore();
244        }
245    }
246
247    @Override
248    public boolean onTouchEvent(MotionEvent ev) {
249        if (System.currentTimeMillis() < mStartTouchTime) {
250            return false;
251        }
252        mBar.resetHeadsUpDecayTimer();
253        return mEdgeSwipeHelper.onTouchEvent(ev)
254                || mSwipeHelper.onTouchEvent(ev)
255                || super.onTouchEvent(ev);
256    }
257
258    @Override
259    protected void onConfigurationChanged(Configuration newConfig) {
260        super.onConfigurationChanged(newConfig);
261        float densityScale = getResources().getDisplayMetrics().density;
262        mSwipeHelper.setDensityScale(densityScale);
263        float pagingTouchSlop = ViewConfiguration.get(getContext()).getScaledPagingTouchSlop();
264        mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
265    }
266
267    // ExpandHelper.Callback methods
268
269    @Override
270    public ExpandableView getChildAtRawPosition(float x, float y) {
271        return getChildAtPosition(x, y);
272    }
273
274    @Override
275    public ExpandableView getChildAtPosition(float x, float y) {
276        return mHeadsUp == null ? null : mHeadsUp.row;
277    }
278
279    @Override
280    public boolean canChildBeExpanded(View v) {
281        return mHeadsUp != null && mHeadsUp.row == v && mHeadsUp.row.isExpandable();
282    }
283
284    @Override
285    public void setUserExpandedChild(View v, boolean userExpanded) {
286        if (mHeadsUp != null && mHeadsUp.row == v) {
287            mHeadsUp.row.setUserExpanded(userExpanded);
288        }
289    }
290
291    @Override
292    public void setUserLockedChild(View v, boolean userLocked) {
293        if (mHeadsUp != null && mHeadsUp.row == v) {
294            mHeadsUp.row.setUserLocked(userLocked);
295        }
296    }
297
298    @Override
299    public void expansionStateChanged(boolean isExpanding) {
300
301    }
302
303    // SwipeHelper.Callback methods
304
305    @Override
306    public boolean canChildBeDismissed(View v) {
307        return true;
308    }
309
310    @Override
311    public boolean isAntiFalsingNeeded() {
312        return false;
313    }
314
315    @Override
316    public float getFalsingThresholdFactor() {
317        return 1.0f;
318    }
319
320    @Override
321    public void onChildDismissed(View v) {
322        Log.v(TAG, "User swiped heads up to dismiss");
323        mBar.onHeadsUpDismissed();
324    }
325
326    @Override
327    public void onBeginDrag(View v) {
328    }
329
330    @Override
331    public void onDragCancelled(View v) {
332        mContentHolder.setAlpha(mMaxAlpha); // sometimes this isn't quite reset
333    }
334
335    @Override
336    public void onChildSnappedBack(View animView) {
337    }
338
339    @Override
340    public boolean updateSwipeProgress(View animView, boolean dismissable, float swipeProgress) {
341        getBackground().setAlpha((int) (255 * swipeProgress));
342        return false;
343    }
344
345    @Override
346    public View getChildAtPosition(MotionEvent ev) {
347        return mContentHolder;
348    }
349
350    @Override
351    public View getChildContentView(View v) {
352        return mContentHolder;
353    }
354
355    @Override
356    public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo info) {
357        mContentHolder.getLocationOnScreen(mTmpTwoArray);
358
359        info.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
360        info.touchableRegion.set(mTmpTwoArray[0], mTmpTwoArray[1],
361                mTmpTwoArray[0] + mContentHolder.getWidth(),
362                mTmpTwoArray[1] + mContentHolder.getHeight());
363    }
364
365    public void escalate() {
366        mBar.scheduleHeadsUpEscalation();
367    }
368
369    public String getKey() {
370        return mHeadsUp == null ? null : mHeadsUp.notification.getKey();
371    }
372
373    private class EdgeSwipeHelper implements Gefingerpoken {
374        private static final boolean DEBUG_EDGE_SWIPE = false;
375        private final float mTouchSlop;
376        private boolean mConsuming;
377        private float mFirstY;
378        private float mFirstX;
379
380        public EdgeSwipeHelper(float touchSlop) {
381            mTouchSlop = touchSlop;
382        }
383
384        @Override
385        public boolean onInterceptTouchEvent(MotionEvent ev) {
386            switch (ev.getActionMasked()) {
387                case MotionEvent.ACTION_DOWN:
388                    if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action down " + ev.getY());
389                    mFirstX = ev.getX();
390                    mFirstY = ev.getY();
391                    mConsuming = false;
392                    break;
393
394                case MotionEvent.ACTION_MOVE:
395                    if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action move " + ev.getY());
396                    final float dY = ev.getY() - mFirstY;
397                    final float daX = Math.abs(ev.getX() - mFirstX);
398                    final float daY = Math.abs(dY);
399                    if (!mConsuming && daX < daY && daY > mTouchSlop) {
400                        if (dY > 0) {
401                            if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found an open");
402                            mBar.animateExpandNotificationsPanel();
403                        }
404                        if (dY < 0) {
405                            if (DEBUG_EDGE_SWIPE) Log.d(TAG, "found a close");
406                            mBar.onHeadsUpDismissed();
407                        }
408                        mConsuming = true;
409                    }
410                    break;
411
412                case MotionEvent.ACTION_UP:
413                case MotionEvent.ACTION_CANCEL:
414                    if (DEBUG_EDGE_SWIPE) Log.d(TAG, "action done" );
415                    mConsuming = false;
416                    break;
417            }
418            return mConsuming;
419        }
420
421        @Override
422        public boolean onTouchEvent(MotionEvent ev) {
423            return mConsuming;
424        }
425    }
426}
427