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.animation.Interpolator;
     31 import android.view.animation.LinearInterpolator;
     32 import android.widget.FrameLayout;
     33 import android.widget.ImageView;
     34 
     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     private static final Paint INVERT_PAINT = createInvertPaint();
     46     private static final ColorFilter NO_COLOR_FILTER = new ColorFilter();
     47 
     48     private final Rect mClipBounds = new Rect();
     49 
     50     private View mContractedChild;
     51     private View mExpandedChild;
     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 
     64     public NotificationContentView(Context context, AttributeSet attrs) {
     65         super(context, attrs);
     66         mFadePaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.ADD));
     67         reset();
     68     }
     69 
     70     @Override
     71     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
     72         super.onLayout(changed, left, top, right, bottom);
     73         updateClipping();
     74     }
     75 
     76     public void reset() {
     77         if (mContractedChild != null) {
     78             mContractedChild.animate().cancel();
     79         }
     80         if (mExpandedChild != null) {
     81             mExpandedChild.animate().cancel();
     82         }
     83         removeAllViews();
     84         mContractedChild = null;
     85         mExpandedChild = null;
     86         mSmallHeight = getResources().getDimensionPixelSize(R.dimen.notification_min_height);
     87         mActualHeight = mSmallHeight;
     88         mContractedVisible = true;
     89     }
     90 
     91     public View getContractedChild() {
     92         return mContractedChild;
     93     }
     94 
     95     public View getExpandedChild() {
     96         return mExpandedChild;
     97     }
     98 
     99     public void setContractedChild(View child) {
    100         if (mContractedChild != null) {
    101             mContractedChild.animate().cancel();
    102             removeView(mContractedChild);
    103         }
    104         sanitizeContractedLayoutParams(child);
    105         addView(child);
    106         mContractedChild = child;
    107         selectLayout(false /* animate */, true /* force */);
    108     }
    109 
    110     public void setExpandedChild(View child) {
    111         if (mExpandedChild != null) {
    112             mExpandedChild.animate().cancel();
    113             removeView(mExpandedChild);
    114         }
    115         addView(child);
    116         mExpandedChild = child;
    117         selectLayout(false /* animate */, true /* force */);
    118     }
    119 
    120     public void setActualHeight(int actualHeight) {
    121         mActualHeight = actualHeight;
    122         selectLayout(true /* animate */, false /* force */);
    123         updateClipping();
    124     }
    125 
    126     public int getMaxHeight() {
    127 
    128         // The maximum height is just the laid out height.
    129         return getHeight();
    130     }
    131 
    132     public int getMinHeight() {
    133         return mSmallHeight;
    134     }
    135 
    136     public void setClipTopAmount(int clipTopAmount) {
    137         mClipTopAmount = clipTopAmount;
    138         updateClipping();
    139     }
    140 
    141     private void updateClipping() {
    142         mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight);
    143         setClipBounds(mClipBounds);
    144     }
    145 
    146     private void sanitizeContractedLayoutParams(View contractedChild) {
    147         LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams();
    148         lp.height = mSmallHeight;
    149         contractedChild.setLayoutParams(lp);
    150     }
    151 
    152     private void selectLayout(boolean animate, boolean force) {
    153         if (mContractedChild == null) {
    154             return;
    155         }
    156         boolean showContractedChild = showContractedChild();
    157         if (showContractedChild != mContractedVisible || force) {
    158             if (animate && mExpandedChild != null) {
    159                 runSwitchAnimation(showContractedChild);
    160             } else if (mExpandedChild != null) {
    161                 mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE);
    162                 mContractedChild.setAlpha(showContractedChild ? 1f : 0f);
    163                 mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE);
    164                 mExpandedChild.setAlpha(showContractedChild ? 0f : 1f);
    165             }
    166         }
    167         mContractedVisible = showContractedChild;
    168     }
    169 
    170     private void runSwitchAnimation(final boolean showContractedChild) {
    171         mContractedChild.setVisibility(View.VISIBLE);
    172         mExpandedChild.setVisibility(View.VISIBLE);
    173         mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
    174         mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
    175         setLayerType(LAYER_TYPE_HARDWARE, null);
    176         mContractedChild.animate()
    177                 .alpha(showContractedChild ? 1f : 0f)
    178                 .setDuration(ANIMATION_DURATION_LENGTH)
    179                 .setInterpolator(mLinearInterpolator);
    180         mExpandedChild.animate()
    181                 .alpha(showContractedChild ? 0f : 1f)
    182                 .setDuration(ANIMATION_DURATION_LENGTH)
    183                 .setInterpolator(mLinearInterpolator)
    184                 .withEndAction(new Runnable() {
    185                     @Override
    186                     public void run() {
    187                         mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
    188                         mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
    189                         setLayerType(LAYER_TYPE_NONE, null);
    190                         mContractedChild.setVisibility(showContractedChild
    191                                 ? View.VISIBLE
    192                                 : View.INVISIBLE);
    193                         mExpandedChild.setVisibility(showContractedChild
    194                                 ? View.INVISIBLE
    195                                 : View.VISIBLE);
    196                     }
    197                 });
    198     }
    199 
    200     private boolean showContractedChild() {
    201         return mActualHeight <= mSmallHeight || mExpandedChild == null;
    202     }
    203 
    204     public void notifyContentUpdated() {
    205         selectLayout(false /* animate */, true /* force */);
    206     }
    207 
    208     public boolean isContentExpandable() {
    209         return mExpandedChild != null;
    210     }
    211 
    212     public void setDark(boolean dark, boolean fade) {
    213         if (mDark == dark || mContractedChild == null) return;
    214         mDark = dark;
    215         setImageViewDark(dark, fade, com.android.internal.R.id.right_icon);
    216         setImageViewDark(dark, fade, com.android.internal.R.id.icon);
    217     }
    218 
    219     private void setImageViewDark(boolean dark, boolean fade, int imageViewId) {
    220         // TODO: implement fade
    221         final ImageView v = (ImageView) mContractedChild.findViewById(imageViewId);
    222         if (v == null) return;
    223         final Drawable d = v.getBackground();
    224         if (dark) {
    225             v.setLayerType(LAYER_TYPE_HARDWARE, INVERT_PAINT);
    226             if (d != null) {
    227                 v.setTag(R.id.doze_saved_filter_tag, d.getColorFilter() != null ? d.getColorFilter()
    228                         : NO_COLOR_FILTER);
    229                 d.setColorFilter(getResources().getColor(R.color.doze_small_icon_background_color),
    230                         PorterDuff.Mode.SRC_ATOP);
    231                 v.setImageAlpha(getResources().getInteger(R.integer.doze_small_icon_alpha));
    232             }
    233         } else {
    234             v.setLayerType(LAYER_TYPE_NONE, null);
    235             if (d != null)  {
    236                 final ColorFilter filter = (ColorFilter) v.getTag(R.id.doze_saved_filter_tag);
    237                 if (filter != null) {
    238                     d.setColorFilter(filter == NO_COLOR_FILTER ? null : filter);
    239                     v.setTag(R.id.doze_saved_filter_tag, null);
    240                 }
    241                 v.setImageAlpha(0xff);
    242             }
    243         }
    244     }
    245 
    246     @Override
    247     public boolean hasOverlappingRendering() {
    248 
    249         // This is not really true, but good enough when fading from the contracted to the expanded
    250         // layout, and saves us some layers.
    251         return false;
    252     }
    253 
    254     private static Paint createInvertPaint() {
    255         final Paint p = new Paint();
    256         final float[] invert = {
    257             -1f,  0f,  0f, 1f, 1f,
    258              0f, -1f,  0f, 1f, 1f,
    259              0f,  0f, -1f, 1f, 1f,
    260              0f,  0f,  0f, 1f, 0f
    261         };
    262         p.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(invert)));
    263         return p;
    264     }
    265 }
    266