Home | History | Annotate | Download | only in statusbar
      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 
     17 package com.android.systemui.statusbar;
     18 
     19 import android.content.Context;
     20 import android.graphics.ColorFilter;
     21 import android.graphics.ColorMatrix;
     22 import android.graphics.ColorMatrixColorFilter;
     23 import android.graphics.Paint;
     24 import android.graphics.PorterDuff;
     25 import android.graphics.PorterDuffXfermode;
     26 import android.graphics.Rect;
     27 import android.graphics.drawable.Drawable;
     28 import android.util.AttributeSet;
     29 import android.view.View;
     30 import android.view.ViewTreeObserver;
     31 import android.view.animation.Interpolator;
     32 import android.view.animation.LinearInterpolator;
     33 import android.widget.FrameLayout;
     34 import android.widget.ImageView;
     35 import com.android.systemui.R;
     36 
     37 /**
     38  * A frame layout containing the actual payload of the notification, including the contracted and
     39  * expanded layout. This class is responsible for clipping the content and and switching between the
     40  * expanded and contracted view depending on its clipped size.
     41  */
     42 public class NotificationContentView extends FrameLayout {
     43 
     44     private static final long ANIMATION_DURATION_LENGTH = 170;
     45 
     46     private final Rect mClipBounds = new Rect();
     47 
     48     private View mContractedChild;
     49     private View mExpandedChild;
     50 
     51     private NotificationViewWrapper mContractedWrapper;
     52 
     53     private int mSmallHeight;
     54     private int mClipTopAmount;
     55     private int mActualHeight;
     56 
     57     private final Interpolator mLinearInterpolator = new LinearInterpolator();
     58 
     59     private boolean mContractedVisible = true;
     60     private boolean mDark;
     61 
     62     private final Paint mFadePaint = new Paint();
     63     private boolean mAnimate;
     64     private ViewTreeObserver.OnPreDrawListener mEnableAnimationPredrawListener
     65             = new ViewTreeObserver.OnPreDrawListener() {
     66         @Override
     67         public boolean onPreDraw() {
     68             mAnimate = true;
     69             getViewTreeObserver().removeOnPreDrawListener(this);
     70             return true;
     71         }
     72     };
     73 
     74     public NotificationContentView(Context context, AttributeSet attrs) {
     75         super(context, attrs);
     76         mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
     77         reset(true);
     78     }
     79 
     80     @Override
     81     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
     82         super.onLayout(changed, left, top, right, bottom);
     83         updateClipping();
     84     }
     85 
     86     @Override
     87     protected void onAttachedToWindow() {
     88         super.onAttachedToWindow();
     89         updateVisibility();
     90     }
     91 
     92     public void reset(boolean resetActualHeight) {
     93         if (mContractedChild != null) {
     94             mContractedChild.animate().cancel();
     95         }
     96         if (mExpandedChild != null) {
     97             mExpandedChild.animate().cancel();
     98         }
     99         removeAllViews();
    100         mContractedChild = null;
    101         mExpandedChild = null;
    102         mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
    103         mContractedVisible = true;
    104         if (resetActualHeight) {
    105             mActualHeight = mSmallHeight;
    106         }
    107     }
    108 
    109     public View getContractedChild() {
    110         return mContractedChild;
    111     }
    112 
    113     public View getExpandedChild() {
    114         return mExpandedChild;
    115     }
    116 
    117     public void setContractedChild(View child) {
    118         if (mContractedChild != null) {
    119             mContractedChild.animate().cancel();
    120             removeView(mContractedChild);
    121         }
    122         sanitizeContractedLayoutParams(child);
    123         addView(child);
    124         mContractedChild = child;
    125         mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child);
    126         selectLayout(false /* animate */, true /* force */);
    127         mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
    128     }
    129 
    130     public void setExpandedChild(View child) {
    131         if (mExpandedChild != null) {
    132             mExpandedChild.animate().cancel();
    133             removeView(mExpandedChild);
    134         }
    135         addView(child);
    136         mExpandedChild = child;
    137         selectLayout(false /* animate */, true /* force */);
    138     }
    139 
    140     @Override
    141     protected void onVisibilityChanged(View changedView, int visibility) {
    142         super.onVisibilityChanged(changedView, visibility);
    143         updateVisibility();
    144     }
    145 
    146     private void updateVisibility() {
    147         setVisible(isShown());
    148     }
    149 
    150     private void setVisible(final boolean isVisible) {
    151         if (isVisible) {
    152 
    153             // We only animate if we are drawn at least once, otherwise the view might animate when
    154             // it's shown the first time
    155             getViewTreeObserver().addOnPreDrawListener(mEnableAnimationPredrawListener);
    156         } else {
    157             getViewTreeObserver().removeOnPreDrawListener(mEnableAnimationPredrawListener);
    158             mAnimate = false;
    159         }
    160     }
    161 
    162     public void setActualHeight(int actualHeight) {
    163         mActualHeight = actualHeight;
    164         selectLayout(mAnimate /* animate */, false /* force */);
    165         updateClipping();
    166     }
    167 
    168     public int getMaxHeight() {
    169 
    170         // The maximum height is just the laid out height.
    171         return getHeight();
    172     }
    173 
    174     public int getMinHeight() {
    175         return mSmallHeight;
    176     }
    177 
    178     public void setClipTopAmount(int clipTopAmount) {
    179         mClipTopAmount = clipTopAmount;
    180         updateClipping();
    181     }
    182 
    183     private void updateClipping() {
    184         mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight);
    185         setClipBounds(mClipBounds);
    186     }
    187 
    188     private void sanitizeContractedLayoutParams(View contractedChild) {
    189         LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams();
    190         lp.height = mSmallHeight;
    191         contractedChild.setLayoutParams(lp);
    192     }
    193 
    194     private void selectLayout(boolean animate, boolean force) {
    195         if (mContractedChild == null) {
    196             return;
    197         }
    198         boolean showContractedChild = showContractedChild();
    199         if (showContractedChild != mContractedVisible || force) {
    200             if (animate && mExpandedChild != null) {
    201                 runSwitchAnimation(showContractedChild);
    202             } else if (mExpandedChild != null) {
    203                 mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE);
    204                 mContractedChild.setAlpha(showContractedChild ? 1f : 0f);
    205                 mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE);
    206                 mExpandedChild.setAlpha(showContractedChild ? 0f : 1f);
    207             }
    208         }
    209         mContractedVisible = showContractedChild;
    210     }
    211 
    212     private void runSwitchAnimation(final boolean showContractedChild) {
    213         mContractedChild.setVisibility(View.VISIBLE);
    214         mExpandedChild.setVisibility(View.VISIBLE);
    215         mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
    216         mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
    217         setLayerType(LAYER_TYPE_HARDWARE, null);
    218         mContractedChild.animate()
    219                 .alpha(showContractedChild ? 1f : 0f)
    220                 .setDuration(ANIMATION_DURATION_LENGTH)
    221                 .setInterpolator(mLinearInterpolator);
    222         mExpandedChild.animate()
    223                 .alpha(showContractedChild ? 0f : 1f)
    224                 .setDuration(ANIMATION_DURATION_LENGTH)
    225                 .setInterpolator(mLinearInterpolator)
    226                 .withEndAction(new Runnable() {
    227                     @Override
    228                     public void run() {
    229                         mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
    230                         mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
    231                         setLayerType(LAYER_TYPE_NONE, null);
    232                         mContractedChild.setVisibility(showContractedChild
    233                                 ? View.VISIBLE
    234                                 : View.INVISIBLE);
    235                         mExpandedChild.setVisibility(showContractedChild
    236                                 ? View.INVISIBLE
    237                                 : View.VISIBLE);
    238                     }
    239                 });
    240     }
    241 
    242     private boolean showContractedChild() {
    243         return mActualHeight <= mSmallHeight || mExpandedChild == null;
    244     }
    245 
    246     public void notifyContentUpdated() {
    247         selectLayout(false /* animate */, true /* force */);
    248         if (mContractedChild != null) {
    249             mContractedWrapper.notifyContentUpdated();
    250             mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
    251         }
    252     }
    253 
    254     public boolean isContentExpandable() {
    255         return mExpandedChild != null;
    256     }
    257 
    258     public void setDark(boolean dark, boolean fade, long delay) {
    259         if (mDark == dark || mContractedChild == null) return;
    260         mDark = dark;
    261         mContractedWrapper.setDark(dark, fade, delay);
    262     }
    263 
    264     @Override
    265     public boolean hasOverlappingRendering() {
    266 
    267         // This is not really true, but good enough when fading from the contracted to the expanded
    268         // layout, and saves us some layers.
    269         return false;
    270     }
    271 }
    272