ExpandableNotificationRow.java revision ccd14fbcfe613eae71f0e00c39f38e5125749389
1/*
2 * Copyright (C) 2013 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.View;
22import android.view.accessibility.AccessibilityEvent;
23
24import com.android.systemui.R;
25
26public class ExpandableNotificationRow extends ActivatableNotificationView {
27    private int mRowMinHeight;
28    private int mRowMaxHeight;
29
30    /** Does this row contain layouts that can adapt to row expansion */
31    private boolean mExpandable;
32    /** Has the user actively changed the expansion state of this row */
33    private boolean mHasUserChangedExpansion;
34    /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
35    private boolean mUserExpanded;
36    /** Is the user touching this row */
37    private boolean mUserLocked;
38    /** Are we showing the "public" version */
39    private boolean mShowingPublic;
40    private boolean mSensitive;
41    private boolean mShowingPublicInitialized;
42    private boolean mShowingPublicForIntrinsicHeight;
43
44    /**
45     * Is this notification expanded by the system. The expansion state can be overridden by the
46     * user expansion.
47     */
48    private boolean mIsSystemExpanded;
49
50    /**
51     * Whether the notification expansion is disabled. This is the case on Keyguard.
52     */
53    private boolean mExpansionDisabled;
54
55    private NotificationContentView mPublicLayout;
56    private NotificationContentView mPrivateLayout;
57    private int mMaxExpandHeight;
58    private View mVetoButton;
59    private boolean mClearable;
60    private ExpansionLogger mLogger;
61    private String mLoggingKey;
62
63    public interface ExpansionLogger {
64        public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
65    }
66
67    public ExpandableNotificationRow(Context context, AttributeSet attrs) {
68        super(context, attrs);
69    }
70
71    /**
72     * Resets this view so it can be re-used for an updated notification.
73     */
74    @Override
75    public void reset() {
76        super.reset();
77        mRowMinHeight = 0;
78        final boolean wasExpanded = isExpanded();
79        mRowMaxHeight = 0;
80        mExpandable = false;
81        mHasUserChangedExpansion = false;
82        mUserLocked = false;
83        mShowingPublic = false;
84        mSensitive = false;
85        mShowingPublicInitialized = false;
86        mIsSystemExpanded = false;
87        mExpansionDisabled = false;
88        mPublicLayout.reset();
89        mPrivateLayout.reset();
90        mMaxExpandHeight = 0;
91        logExpansionEvent(false, wasExpanded);
92    }
93
94    @Override
95    protected void onFinishInflate() {
96        super.onFinishInflate();
97        mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
98        mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
99        mVetoButton = findViewById(R.id.veto);
100    }
101
102    @Override
103    public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
104        if (super.onRequestSendAccessibilityEvent(child, event)) {
105            // Add a record for the entire layout since its content is somehow small.
106            // The event comes from a leaf view that is interacted with.
107            AccessibilityEvent record = AccessibilityEvent.obtain();
108            onInitializeAccessibilityEvent(record);
109            dispatchPopulateAccessibilityEvent(record);
110            event.appendRecord(record);
111            return true;
112        }
113        return false;
114    }
115
116    public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
117        mRowMinHeight = rowMinHeight;
118        mRowMaxHeight = rowMaxHeight;
119    }
120
121    public boolean isExpandable() {
122        return mExpandable;
123    }
124
125    public void setExpandable(boolean expandable) {
126        mExpandable = expandable;
127    }
128
129    /**
130     * @return whether the user has changed the expansion state
131     */
132    public boolean hasUserChangedExpansion() {
133        return mHasUserChangedExpansion;
134    }
135
136    public boolean isUserExpanded() {
137        return mUserExpanded;
138    }
139
140    /**
141     * Set this notification to be expanded by the user
142     *
143     * @param userExpanded whether the user wants this notification to be expanded
144     */
145    public void setUserExpanded(boolean userExpanded) {
146        if (userExpanded && !mExpandable) return;
147        final boolean wasExpanded = isExpanded();
148        mHasUserChangedExpansion = true;
149        mUserExpanded = userExpanded;
150        logExpansionEvent(true, wasExpanded);
151    }
152
153    public void resetUserExpansion() {
154        mHasUserChangedExpansion = false;
155        mUserExpanded = false;
156    }
157
158    public boolean isUserLocked() {
159        return mUserLocked;
160    }
161
162    public void setUserLocked(boolean userLocked) {
163        mUserLocked = userLocked;
164    }
165
166    /**
167     * @return has the system set this notification to be expanded
168     */
169    public boolean isSystemExpanded() {
170        return mIsSystemExpanded;
171    }
172
173    /**
174     * Set this notification to be expanded by the system.
175     *
176     * @param expand whether the system wants this notification to be expanded.
177     */
178    public void setSystemExpanded(boolean expand) {
179        final boolean wasExpanded = isExpanded();
180        mIsSystemExpanded = expand;
181        notifyHeightChanged();
182        logExpansionEvent(false, wasExpanded);
183    }
184
185    /**
186     * @param expansionDisabled whether to prevent notification expansion
187     */
188    public void setExpansionDisabled(boolean expansionDisabled) {
189        final boolean wasExpanded = isExpanded();
190        mExpansionDisabled = expansionDisabled;
191        logExpansionEvent(false, wasExpanded);
192        notifyHeightChanged();
193    }
194
195    /**
196     * @return Can the underlying notification be cleared?
197     */
198    public boolean isClearable() {
199        return mClearable;
200    }
201
202    /**
203     * Set whether the notification can be cleared.
204     *
205     * @param clearable
206     */
207    public void setClearable(boolean clearable) {
208        mClearable = clearable;
209        updateVetoButton();
210    }
211
212    /**
213     * Apply an expansion state to the layout.
214     */
215    public void applyExpansionToLayout() {
216        boolean expand = isExpanded();
217        if (expand && mExpandable) {
218            setActualHeight(mMaxExpandHeight);
219        } else {
220            setActualHeight(mRowMinHeight);
221        }
222    }
223
224    @Override
225    public int getIntrinsicHeight() {
226        if (isUserLocked()) {
227            return getActualHeight();
228        }
229        boolean inExpansionState = isExpanded();
230        if (!inExpansionState) {
231            // not expanded, so we return the collapsed size
232            return mRowMinHeight;
233        }
234
235        return mShowingPublicForIntrinsicHeight ? mRowMinHeight : getMaxExpandHeight();
236    }
237
238    /**
239     * Check whether the view state is currently expanded. This is given by the system in {@link
240     * #setSystemExpanded(boolean)} and can be overridden by user expansion or
241     * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
242     * view can differ from this state, if layout params are modified from outside.
243     *
244     * @return whether the view state is currently expanded.
245     */
246    private boolean isExpanded() {
247        return !mExpansionDisabled
248                && (!hasUserChangedExpansion() && isSystemExpanded() || isUserExpanded());
249    }
250
251    @Override
252    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
253        super.onLayout(changed, left, top, right, bottom);
254        boolean updateExpandHeight = mMaxExpandHeight == 0;
255        mMaxExpandHeight = mPrivateLayout.getMaxHeight();
256        if (updateExpandHeight) {
257            applyExpansionToLayout();
258        }
259    }
260
261    public void setSensitive(boolean sensitive) {
262        mSensitive = sensitive;
263    }
264
265    public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
266        mShowingPublicForIntrinsicHeight = mSensitive && hideSensitive;
267    }
268
269    public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
270            long duration) {
271        boolean oldShowingPublic = mShowingPublic;
272        mShowingPublic = mSensitive && hideSensitive;
273        if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
274            return;
275        }
276
277        // bail out if no public version
278        if (mPublicLayout.getChildCount() == 0) return;
279
280        if (!animated) {
281            mPublicLayout.animate().cancel();
282            mPrivateLayout.animate().cancel();
283            mPublicLayout.setAlpha(1f);
284            mPrivateLayout.setAlpha(1f);
285            mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
286            mPrivateLayout.setVisibility(mShowingPublic ? View.INVISIBLE : View.VISIBLE);
287        } else {
288            animateShowingPublic(delay, duration);
289        }
290
291        updateVetoButton();
292        mShowingPublicInitialized = true;
293    }
294
295    private void animateShowingPublic(long delay, long duration) {
296        final View source = mShowingPublic ? mPrivateLayout : mPublicLayout;
297        View target = mShowingPublic ? mPublicLayout : mPrivateLayout;
298        source.setVisibility(View.VISIBLE);
299        target.setVisibility(View.VISIBLE);
300        target.setAlpha(0f);
301        source.animate().cancel();
302        target.animate().cancel();
303        source.animate()
304                .alpha(0f)
305                .withLayer()
306                .setStartDelay(delay)
307                .setDuration(duration)
308                .withEndAction(new Runnable() {
309                    @Override
310                    public void run() {
311                        source.setVisibility(View.INVISIBLE);
312                    }
313                });
314        target.animate()
315                .alpha(1f)
316                .withLayer()
317                .setStartDelay(delay)
318                .setDuration(duration);
319    }
320
321    private void updateVetoButton() {
322        // public versions cannot be dismissed
323        mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
324    }
325
326    public int getMaxExpandHeight() {
327        return mShowingPublicForIntrinsicHeight ? mRowMinHeight : mMaxExpandHeight;
328    }
329
330    @Override
331    public boolean isContentExpandable() {
332        NotificationContentView showingLayout = getShowingLayout();
333        return showingLayout.isContentExpandable();
334    }
335
336    @Override
337    public void setActualHeight(int height, boolean notifyListeners) {
338        mPrivateLayout.setActualHeight(height);
339        mPublicLayout.setActualHeight(height);
340        invalidate();
341        super.setActualHeight(height, notifyListeners);
342    }
343
344    @Override
345    public int getMaxHeight() {
346        NotificationContentView showingLayout = getShowingLayout();
347        return showingLayout.getMaxHeight();
348    }
349
350    @Override
351    public int getMinHeight() {
352        NotificationContentView showingLayout = getShowingLayout();
353        return showingLayout.getMinHeight();
354    }
355
356    @Override
357    public void setClipTopAmount(int clipTopAmount) {
358        super.setClipTopAmount(clipTopAmount);
359        mPrivateLayout.setClipTopAmount(clipTopAmount);
360        mPublicLayout.setClipTopAmount(clipTopAmount);
361    }
362
363    public void notifyContentUpdated() {
364        mPublicLayout.notifyContentUpdated();
365        mPrivateLayout.notifyContentUpdated();
366    }
367
368    public boolean isShowingLayoutLayouted() {
369        NotificationContentView showingLayout = getShowingLayout();
370        return showingLayout.getWidth() != 0;
371    }
372
373    private NotificationContentView getShowingLayout() {
374        return mShowingPublic ? mPublicLayout : mPrivateLayout;
375    }
376
377    public void setExpansionLogger(ExpansionLogger logger, String key) {
378        mLogger = logger;
379        mLoggingKey = key;
380    }
381
382
383    private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
384        final boolean nowExpanded = isExpanded();
385        if (wasExpanded != nowExpanded && mLogger != null) {
386            mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ;
387        }
388    }
389}
390