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