ActivatableNotificationView.java revision be565dfc1c17b7ddafa9753851b8f82849fd3f42
1/*
2 * Copyright (C) 2014 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;
18
19import android.content.Context;
20import android.util.AttributeSet;
21import android.view.MotionEvent;
22import android.view.View;
23import android.view.ViewConfiguration;
24import android.widget.FrameLayout;
25
26import com.android.internal.R;
27
28/**
29 * Base class for both {@link ExpandableNotificationRow} and {@link NotificationOverflowContainer}
30 * to implement dimming/activating on Keyguard for the double-tap gesture
31 */
32public abstract class ActivatableNotificationView extends ExpandableOutlineView {
33
34    private static final long DOUBLETAP_TIMEOUT_MS = 1000;
35
36    private boolean mDimmed;
37    private boolean mLocked;
38
39    private int mBgResId = R.drawable.notification_quantum_bg;
40    private int mDimmedBgResId = R.drawable.notification_quantum_bg_dim;
41
42    /**
43     * Flag to indicate that the notification has been touched once and the second touch will
44     * click it.
45     */
46    private boolean mActivated;
47
48    private float mDownX;
49    private float mDownY;
50    private final float mTouchSlop;
51
52    private OnActivatedListener mOnActivatedListener;
53
54    public ActivatableNotificationView(Context context, AttributeSet attrs) {
55        super(context, attrs);
56        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
57        updateBackgroundResource();
58    }
59
60
61    private final Runnable mTapTimeoutRunnable = new Runnable() {
62        @Override
63        public void run() {
64            makeInactive();
65        }
66    };
67
68    @Override
69    public void setOnClickListener(OnClickListener l) {
70        super.setOnClickListener(l);
71    }
72
73    @Override
74    public boolean onTouchEvent(MotionEvent event) {
75        if (mLocked) {
76            return handleTouchEventLocked(event);
77        } else {
78            return super.onTouchEvent(event);
79        }
80    }
81
82    private boolean handleTouchEventLocked(MotionEvent event) {
83        int action = event.getActionMasked();
84        switch (action) {
85            case MotionEvent.ACTION_DOWN:
86                mDownX = event.getX();
87                mDownY = event.getY();
88                if (mDownY > getActualHeight()) {
89                    return false;
90                }
91
92                // Call the listener tentatively directly, even if we don't know whether the user
93                // will stay within the touch slop, as the listener is implemented as a scale
94                // animation, which is cancellable without jarring effects when swiping away
95                // notifications.
96                if (mOnActivatedListener != null) {
97                    mOnActivatedListener.onActivated(this);
98                }
99                break;
100            case MotionEvent.ACTION_MOVE:
101                if (!isWithinTouchSlop(event)) {
102                    makeInactive();
103                    return false;
104                }
105                break;
106            case MotionEvent.ACTION_UP:
107                if (isWithinTouchSlop(event)) {
108                    if (!mActivated) {
109                        makeActive(event.getX(), event.getY());
110                        postDelayed(mTapTimeoutRunnable, DOUBLETAP_TIMEOUT_MS);
111                    } else {
112                        performClick();
113                        makeInactive();
114                    }
115                } else {
116                    makeInactive();
117                }
118                break;
119            case MotionEvent.ACTION_CANCEL:
120                makeInactive();
121                break;
122            default:
123                break;
124        }
125        return true;
126    }
127
128    private void makeActive(float x, float y) {
129        mCustomBackground.setHotspot(0, x, y);
130        mActivated = true;
131    }
132
133    /**
134     * Cancels the hotspot and makes the notification inactive.
135     */
136    private void makeInactive() {
137        if (mActivated) {
138            // Make sure that we clear the hotspot from the center.
139            mCustomBackground.setHotspot(0, getWidth() / 2, getActualHeight() / 2);
140            mCustomBackground.removeHotspot(0);
141            mActivated = false;
142        }
143        if (mOnActivatedListener != null) {
144            mOnActivatedListener.onReset(this);
145        }
146        removeCallbacks(mTapTimeoutRunnable);
147    }
148
149    private boolean isWithinTouchSlop(MotionEvent event) {
150        return Math.abs(event.getX() - mDownX) < mTouchSlop
151                && Math.abs(event.getY() - mDownY) < mTouchSlop;
152    }
153
154    /**
155     * Sets the notification as dimmed, meaning that it will appear in a more gray variant.
156     */
157    public void setDimmed(boolean dimmed) {
158        if (mDimmed != dimmed) {
159            mDimmed = dimmed;
160            updateBackgroundResource();
161        }
162    }
163
164    /**
165     * Sets the notification as locked. In the locked state, the first tap will produce a quantum
166     * ripple to make the notification brighter and only the second tap will cause a click.
167     */
168    public void setLocked(boolean locked) {
169        mLocked = locked;
170    }
171
172    /**
173     * Sets the resource id for the background of this notification.
174     *
175     * @param bgResId The background resource to use in normal state.
176     * @param dimmedBgResId The background resource to use in dimmed state.
177     */
178    public void setBackgroundResourceIds(int bgResId, int dimmedBgResId) {
179        mBgResId = bgResId;
180        mDimmedBgResId = dimmedBgResId;
181        updateBackgroundResource();
182    }
183
184    private void updateBackgroundResource() {
185        setCustomBackgroundResource(mDimmed ? mDimmedBgResId : mBgResId);
186    }
187
188    @Override
189    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
190        super.onLayout(changed, left, top, right, bottom);
191        setPivotX(getWidth()/2);
192    }
193
194    @Override
195    public void setActualHeight(int actualHeight) {
196        super.setActualHeight(actualHeight);
197        setPivotY(actualHeight/2);
198    }
199
200    public void setOnActivatedListener(OnActivatedListener onActivatedListener) {
201        mOnActivatedListener = onActivatedListener;
202    }
203
204    public interface OnActivatedListener {
205        void onActivated(View view);
206        void onReset(View view);
207    }
208}
209