Home | History | Annotate | Download | only in statusbar
      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 
     17 package com.android.systemui.statusbar;
     18 
     19 import android.content.Context;
     20 import android.graphics.drawable.AnimatedVectorDrawable;
     21 import android.graphics.drawable.AnimationDrawable;
     22 import android.graphics.drawable.Drawable;
     23 import android.service.notification.StatusBarNotification;
     24 import android.util.AttributeSet;
     25 import android.view.View;
     26 import android.view.ViewGroup;
     27 import android.view.ViewStub;
     28 import android.view.accessibility.AccessibilityEvent;
     29 
     30 import android.widget.ImageView;
     31 import com.android.systemui.R;
     32 
     33 public class ExpandableNotificationRow extends ActivatableNotificationView {
     34     private int mRowMinHeight;
     35     private int mRowMaxHeight;
     36 
     37     /** Does this row contain layouts that can adapt to row expansion */
     38     private boolean mExpandable;
     39     /** Has the user actively changed the expansion state of this row */
     40     private boolean mHasUserChangedExpansion;
     41     /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */
     42     private boolean mUserExpanded;
     43     /** Is the user touching this row */
     44     private boolean mUserLocked;
     45     /** Are we showing the "public" version */
     46     private boolean mShowingPublic;
     47     private boolean mSensitive;
     48     private boolean mShowingPublicInitialized;
     49     private boolean mShowingPublicForIntrinsicHeight;
     50 
     51     /**
     52      * Is this notification expanded by the system. The expansion state can be overridden by the
     53      * user expansion.
     54      */
     55     private boolean mIsSystemExpanded;
     56 
     57     /**
     58      * Whether the notification expansion is disabled. This is the case on Keyguard.
     59      */
     60     private boolean mExpansionDisabled;
     61 
     62     private NotificationContentView mPublicLayout;
     63     private NotificationContentView mPrivateLayout;
     64     private int mMaxExpandHeight;
     65     private View mVetoButton;
     66     private boolean mClearable;
     67     private ExpansionLogger mLogger;
     68     private String mLoggingKey;
     69     private boolean mWasReset;
     70     private NotificationGuts mGuts;
     71 
     72     private StatusBarNotification mStatusBarNotification;
     73 
     74     public void setIconAnimationRunning(boolean running) {
     75         setIconAnimationRunning(running, mPublicLayout);
     76         setIconAnimationRunning(running, mPrivateLayout);
     77     }
     78 
     79     private void setIconAnimationRunning(boolean running, NotificationContentView layout) {
     80         if (layout != null) {
     81             View contractedChild = layout.getContractedChild();
     82             View expandedChild = layout.getExpandedChild();
     83             setIconAnimationRunningForChild(running, contractedChild);
     84             setIconAnimationRunningForChild(running, expandedChild);
     85         }
     86     }
     87 
     88     private void setIconAnimationRunningForChild(boolean running, View child) {
     89         if (child != null) {
     90             ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon);
     91             setIconRunning(icon, running);
     92             ImageView rightIcon = (ImageView) child.findViewById(
     93                     com.android.internal.R.id.right_icon);
     94             setIconRunning(rightIcon, running);
     95         }
     96     }
     97 
     98     private void setIconRunning(ImageView imageView, boolean running) {
     99         if (imageView != null) {
    100             Drawable drawable = imageView.getDrawable();
    101             if (drawable instanceof AnimationDrawable) {
    102                 AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
    103                 if (running) {
    104                     animationDrawable.start();
    105                 } else {
    106                     animationDrawable.stop();
    107                 }
    108             } else if (drawable instanceof AnimatedVectorDrawable) {
    109                 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable;
    110                 if (running) {
    111                     animationDrawable.start();
    112                 } else {
    113                     animationDrawable.stop();
    114                 }
    115             }
    116         }
    117     }
    118 
    119     public void setStatusBarNotification(StatusBarNotification statusBarNotification) {
    120         mStatusBarNotification = statusBarNotification;
    121     }
    122 
    123     public StatusBarNotification getStatusBarNotification() {
    124         return mStatusBarNotification;
    125     }
    126 
    127     public interface ExpansionLogger {
    128         public void logNotificationExpansion(String key, boolean userAction, boolean expanded);
    129     }
    130 
    131     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
    132         super(context, attrs);
    133     }
    134 
    135     /**
    136      * Resets this view so it can be re-used for an updated notification.
    137      */
    138     @Override
    139     public void reset() {
    140         super.reset();
    141         mRowMinHeight = 0;
    142         final boolean wasExpanded = isExpanded();
    143         mRowMaxHeight = 0;
    144         mExpandable = false;
    145         mHasUserChangedExpansion = false;
    146         mUserLocked = false;
    147         mShowingPublic = false;
    148         mSensitive = false;
    149         mShowingPublicInitialized = false;
    150         mIsSystemExpanded = false;
    151         mExpansionDisabled = false;
    152         mPublicLayout.reset();
    153         mPrivateLayout.reset();
    154         resetHeight();
    155         logExpansionEvent(false, wasExpanded);
    156     }
    157 
    158     public void resetHeight() {
    159         mMaxExpandHeight = 0;
    160         mWasReset = true;
    161         onHeightReset();
    162         requestLayout();
    163     }
    164 
    165     @Override
    166     protected void onFinishInflate() {
    167         super.onFinishInflate();
    168         mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic);
    169         mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded);
    170         ViewStub gutsStub = (ViewStub) findViewById(R.id.notification_guts_stub);
    171         gutsStub.setOnInflateListener(new ViewStub.OnInflateListener() {
    172             @Override
    173             public void onInflate(ViewStub stub, View inflated) {
    174                 mGuts = (NotificationGuts) inflated;
    175                 mGuts.setClipTopAmount(getClipTopAmount());
    176                 mGuts.setActualHeight(getActualHeight());
    177             }
    178         });
    179         mVetoButton = findViewById(R.id.veto);
    180     }
    181 
    182     @Override
    183     public boolean onRequestSendAccessibilityEvent(View child, AccessibilityEvent event) {
    184         if (super.onRequestSendAccessibilityEvent(child, event)) {
    185             // Add a record for the entire layout since its content is somehow small.
    186             // The event comes from a leaf view that is interacted with.
    187             AccessibilityEvent record = AccessibilityEvent.obtain();
    188             onInitializeAccessibilityEvent(record);
    189             dispatchPopulateAccessibilityEvent(record);
    190             event.appendRecord(record);
    191             return true;
    192         }
    193         return false;
    194     }
    195 
    196     @Override
    197     public void setDark(boolean dark, boolean fade) {
    198         super.setDark(dark, fade);
    199         final NotificationContentView showing = getShowingLayout();
    200         if (showing != null) {
    201             showing.setDark(dark, fade);
    202         }
    203     }
    204 
    205     public void setHeightRange(int rowMinHeight, int rowMaxHeight) {
    206         mRowMinHeight = rowMinHeight;
    207         mRowMaxHeight = rowMaxHeight;
    208     }
    209 
    210     public boolean isExpandable() {
    211         return mExpandable;
    212     }
    213 
    214     public void setExpandable(boolean expandable) {
    215         mExpandable = expandable;
    216     }
    217 
    218     /**
    219      * @return whether the user has changed the expansion state
    220      */
    221     public boolean hasUserChangedExpansion() {
    222         return mHasUserChangedExpansion;
    223     }
    224 
    225     public boolean isUserExpanded() {
    226         return mUserExpanded;
    227     }
    228 
    229     /**
    230      * Set this notification to be expanded by the user
    231      *
    232      * @param userExpanded whether the user wants this notification to be expanded
    233      */
    234     public void setUserExpanded(boolean userExpanded) {
    235         if (userExpanded && !mExpandable) return;
    236         final boolean wasExpanded = isExpanded();
    237         mHasUserChangedExpansion = true;
    238         mUserExpanded = userExpanded;
    239         logExpansionEvent(true, wasExpanded);
    240     }
    241 
    242     public void resetUserExpansion() {
    243         mHasUserChangedExpansion = false;
    244         mUserExpanded = false;
    245     }
    246 
    247     public boolean isUserLocked() {
    248         return mUserLocked;
    249     }
    250 
    251     public void setUserLocked(boolean userLocked) {
    252         mUserLocked = userLocked;
    253     }
    254 
    255     /**
    256      * @return has the system set this notification to be expanded
    257      */
    258     public boolean isSystemExpanded() {
    259         return mIsSystemExpanded;
    260     }
    261 
    262     /**
    263      * Set this notification to be expanded by the system.
    264      *
    265      * @param expand whether the system wants this notification to be expanded.
    266      */
    267     public void setSystemExpanded(boolean expand) {
    268         if (expand != mIsSystemExpanded) {
    269             final boolean wasExpanded = isExpanded();
    270             mIsSystemExpanded = expand;
    271             notifyHeightChanged();
    272             logExpansionEvent(false, wasExpanded);
    273         }
    274     }
    275 
    276     /**
    277      * @param expansionDisabled whether to prevent notification expansion
    278      */
    279     public void setExpansionDisabled(boolean expansionDisabled) {
    280         if (expansionDisabled != mExpansionDisabled) {
    281             final boolean wasExpanded = isExpanded();
    282             mExpansionDisabled = expansionDisabled;
    283             logExpansionEvent(false, wasExpanded);
    284             if (wasExpanded != isExpanded()) {
    285                 notifyHeightChanged();
    286             }
    287         }
    288     }
    289 
    290     /**
    291      * @return Can the underlying notification be cleared?
    292      */
    293     public boolean isClearable() {
    294         return mClearable;
    295     }
    296 
    297     /**
    298      * Set whether the notification can be cleared.
    299      *
    300      * @param clearable
    301      */
    302     public void setClearable(boolean clearable) {
    303         mClearable = clearable;
    304         updateVetoButton();
    305     }
    306 
    307     /**
    308      * Apply an expansion state to the layout.
    309      */
    310     public void applyExpansionToLayout() {
    311         boolean expand = isExpanded();
    312         if (expand && mExpandable) {
    313             setActualHeight(mMaxExpandHeight);
    314         } else {
    315             setActualHeight(mRowMinHeight);
    316         }
    317     }
    318 
    319     @Override
    320     public int getIntrinsicHeight() {
    321         if (isUserLocked()) {
    322             return getActualHeight();
    323         }
    324         boolean inExpansionState = isExpanded();
    325         if (!inExpansionState) {
    326             // not expanded, so we return the collapsed size
    327             return mRowMinHeight;
    328         }
    329 
    330         return mShowingPublicForIntrinsicHeight ? mRowMinHeight : getMaxExpandHeight();
    331     }
    332 
    333     /**
    334      * Check whether the view state is currently expanded. This is given by the system in {@link
    335      * #setSystemExpanded(boolean)} and can be overridden by user expansion or
    336      * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this
    337      * view can differ from this state, if layout params are modified from outside.
    338      *
    339      * @return whether the view state is currently expanded.
    340      */
    341     private boolean isExpanded() {
    342         return !mExpansionDisabled
    343                 && (!hasUserChangedExpansion() && isSystemExpanded() || isUserExpanded());
    344     }
    345 
    346     @Override
    347     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    348         super.onLayout(changed, left, top, right, bottom);
    349         boolean updateExpandHeight = mMaxExpandHeight == 0 && !mWasReset;
    350         updateMaxExpandHeight();
    351         if (updateExpandHeight) {
    352             applyExpansionToLayout();
    353         }
    354         mWasReset = false;
    355     }
    356 
    357     private void updateMaxExpandHeight() {
    358         int intrinsicBefore = getIntrinsicHeight();
    359         mMaxExpandHeight = mPrivateLayout.getMaxHeight();
    360         if (intrinsicBefore != getIntrinsicHeight()) {
    361             notifyHeightChanged();
    362         }
    363     }
    364 
    365     public void setSensitive(boolean sensitive) {
    366         mSensitive = sensitive;
    367     }
    368 
    369     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
    370         mShowingPublicForIntrinsicHeight = mSensitive && hideSensitive;
    371     }
    372 
    373     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
    374             long duration) {
    375         boolean oldShowingPublic = mShowingPublic;
    376         mShowingPublic = mSensitive && hideSensitive;
    377         if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) {
    378             return;
    379         }
    380 
    381         // bail out if no public version
    382         if (mPublicLayout.getChildCount() == 0) return;
    383 
    384         if (!animated) {
    385             mPublicLayout.animate().cancel();
    386             mPrivateLayout.animate().cancel();
    387             mPublicLayout.setAlpha(1f);
    388             mPrivateLayout.setAlpha(1f);
    389             mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE);
    390             mPrivateLayout.setVisibility(mShowingPublic ? View.INVISIBLE : View.VISIBLE);
    391         } else {
    392             animateShowingPublic(delay, duration);
    393         }
    394 
    395         updateVetoButton();
    396         mShowingPublicInitialized = true;
    397     }
    398 
    399     private void animateShowingPublic(long delay, long duration) {
    400         final View source = mShowingPublic ? mPrivateLayout : mPublicLayout;
    401         View target = mShowingPublic ? mPublicLayout : mPrivateLayout;
    402         source.setVisibility(View.VISIBLE);
    403         target.setVisibility(View.VISIBLE);
    404         target.setAlpha(0f);
    405         source.animate().cancel();
    406         target.animate().cancel();
    407         source.animate()
    408                 .alpha(0f)
    409                 .setStartDelay(delay)
    410                 .setDuration(duration)
    411                 .withEndAction(new Runnable() {
    412                     @Override
    413                     public void run() {
    414                         source.setVisibility(View.INVISIBLE);
    415                     }
    416                 });
    417         target.animate()
    418                 .alpha(1f)
    419                 .setStartDelay(delay)
    420                 .setDuration(duration);
    421     }
    422 
    423     private void updateVetoButton() {
    424         // public versions cannot be dismissed
    425         mVetoButton.setVisibility(isClearable() && !mShowingPublic ? View.VISIBLE : View.GONE);
    426     }
    427 
    428     public int getMaxExpandHeight() {
    429         return mShowingPublicForIntrinsicHeight ? mRowMinHeight : mMaxExpandHeight;
    430     }
    431 
    432     @Override
    433     public boolean isContentExpandable() {
    434         NotificationContentView showingLayout = getShowingLayout();
    435         return showingLayout.isContentExpandable();
    436     }
    437 
    438     @Override
    439     public void setActualHeight(int height, boolean notifyListeners) {
    440         mPrivateLayout.setActualHeight(height);
    441         mPublicLayout.setActualHeight(height);
    442         if (mGuts != null) {
    443             mGuts.setActualHeight(height);
    444         }
    445         invalidate();
    446         super.setActualHeight(height, notifyListeners);
    447     }
    448 
    449     @Override
    450     public int getMaxHeight() {
    451         NotificationContentView showingLayout = getShowingLayout();
    452         return showingLayout.getMaxHeight();
    453     }
    454 
    455     @Override
    456     public int getMinHeight() {
    457         NotificationContentView showingLayout = getShowingLayout();
    458         return showingLayout.getMinHeight();
    459     }
    460 
    461     @Override
    462     public void setClipTopAmount(int clipTopAmount) {
    463         super.setClipTopAmount(clipTopAmount);
    464         mPrivateLayout.setClipTopAmount(clipTopAmount);
    465         mPublicLayout.setClipTopAmount(clipTopAmount);
    466         if (mGuts != null) {
    467             mGuts.setClipTopAmount(clipTopAmount);
    468         }
    469     }
    470 
    471     public void notifyContentUpdated() {
    472         mPublicLayout.notifyContentUpdated();
    473         mPrivateLayout.notifyContentUpdated();
    474     }
    475 
    476     public boolean isMaxExpandHeightInitialized() {
    477         return mMaxExpandHeight != 0;
    478     }
    479 
    480     private NotificationContentView getShowingLayout() {
    481         return mShowingPublic ? mPublicLayout : mPrivateLayout;
    482     }
    483 
    484     public void setExpansionLogger(ExpansionLogger logger, String key) {
    485         mLogger = logger;
    486         mLoggingKey = key;
    487     }
    488 
    489 
    490     private void logExpansionEvent(boolean userAction, boolean wasExpanded) {
    491         final boolean nowExpanded = isExpanded();
    492         if (wasExpanded != nowExpanded && mLogger != null) {
    493             mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ;
    494         }
    495     }
    496 }
    497