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