NotificationContentView.java revision 1bf47b91179a3ebb43dbf3fa6c21a365f595882d
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
17package com.android.systemui.statusbar;
18
19import android.content.Context;
20import android.graphics.ColorFilter;
21import android.graphics.ColorMatrix;
22import android.graphics.ColorMatrixColorFilter;
23import android.graphics.Paint;
24import android.graphics.PorterDuff;
25import android.graphics.PorterDuffXfermode;
26import android.graphics.Rect;
27import android.graphics.drawable.Drawable;
28import android.util.AttributeSet;
29import android.view.View;
30import android.view.animation.Interpolator;
31import android.view.animation.LinearInterpolator;
32import android.widget.FrameLayout;
33import android.widget.ImageView;
34
35import 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 */
42public 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 void setContractedChild(View child) {
92        if (mContractedChild != null) {
93            mContractedChild.animate().cancel();
94            removeView(mContractedChild);
95        }
96        sanitizeContractedLayoutParams(child);
97        addView(child);
98        mContractedChild = child;
99        selectLayout(false /* animate */, true /* force */);
100    }
101
102    public void setExpandedChild(View child) {
103        if (mExpandedChild != null) {
104            mExpandedChild.animate().cancel();
105            removeView(mExpandedChild);
106        }
107        addView(child);
108        mExpandedChild = child;
109        selectLayout(false /* animate */, true /* force */);
110    }
111
112    public void setActualHeight(int actualHeight) {
113        mActualHeight = actualHeight;
114        selectLayout(true /* animate */, false /* force */);
115        updateClipping();
116    }
117
118    public int getMaxHeight() {
119
120        // The maximum height is just the laid out height.
121        return getHeight();
122    }
123
124    public int getMinHeight() {
125        return mSmallHeight;
126    }
127
128    public void setClipTopAmount(int clipTopAmount) {
129        mClipTopAmount = clipTopAmount;
130        updateClipping();
131    }
132
133    private void updateClipping() {
134        mClipBounds.set(0, mClipTopAmount, getWidth(), mActualHeight);
135        setClipBounds(mClipBounds);
136    }
137
138    private void sanitizeContractedLayoutParams(View contractedChild) {
139        LayoutParams lp = (LayoutParams) contractedChild.getLayoutParams();
140        lp.height = mSmallHeight;
141        contractedChild.setLayoutParams(lp);
142    }
143
144    private void selectLayout(boolean animate, boolean force) {
145        if (mContractedChild == null) {
146            return;
147        }
148        boolean showContractedChild = showContractedChild();
149        if (showContractedChild != mContractedVisible || force) {
150            if (animate && mExpandedChild != null) {
151                runSwitchAnimation(showContractedChild);
152            } else if (mExpandedChild != null) {
153                mContractedChild.setVisibility(showContractedChild ? View.VISIBLE : View.INVISIBLE);
154                mContractedChild.setAlpha(showContractedChild ? 1f : 0f);
155                mExpandedChild.setVisibility(showContractedChild ? View.INVISIBLE : View.VISIBLE);
156                mExpandedChild.setAlpha(showContractedChild ? 0f : 1f);
157            }
158        }
159        mContractedVisible = showContractedChild;
160    }
161
162    private void runSwitchAnimation(final boolean showContractedChild) {
163        mContractedChild.setVisibility(View.VISIBLE);
164        mExpandedChild.setVisibility(View.VISIBLE);
165        mContractedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
166        mExpandedChild.setLayerType(LAYER_TYPE_HARDWARE, mFadePaint);
167        setLayerType(LAYER_TYPE_HARDWARE, null);
168        mContractedChild.animate()
169                .alpha(showContractedChild ? 1f : 0f)
170                .setDuration(ANIMATION_DURATION_LENGTH)
171                .setInterpolator(mLinearInterpolator);
172        mExpandedChild.animate()
173                .alpha(showContractedChild ? 0f : 1f)
174                .setDuration(ANIMATION_DURATION_LENGTH)
175                .setInterpolator(mLinearInterpolator)
176                .withEndAction(new Runnable() {
177                    @Override
178                    public void run() {
179                        mContractedChild.setLayerType(LAYER_TYPE_NONE, null);
180                        mExpandedChild.setLayerType(LAYER_TYPE_NONE, null);
181                        setLayerType(LAYER_TYPE_NONE, null);
182                        mContractedChild.setVisibility(showContractedChild
183                                ? View.VISIBLE
184                                : View.INVISIBLE);
185                        mExpandedChild.setVisibility(showContractedChild
186                                ? View.INVISIBLE
187                                : View.VISIBLE);
188                    }
189                });
190    }
191
192    private boolean showContractedChild() {
193        return mActualHeight <= mSmallHeight || mExpandedChild == null;
194    }
195
196    public void notifyContentUpdated() {
197        selectLayout(false /* animate */, true /* force */);
198    }
199
200    public boolean isContentExpandable() {
201        return mExpandedChild != null;
202    }
203
204    public void setDark(boolean dark, boolean fade) {
205        if (mDark == dark) return;
206        mDark = dark;
207        setImageViewDark(dark, fade, com.android.internal.R.id.right_icon);
208        setImageViewDark(dark, fade, com.android.internal.R.id.icon);
209    }
210
211    private void setImageViewDark(boolean dark, boolean fade, int imageViewId) {
212        // TODO: implement fade
213        final ImageView v = (ImageView) mContractedChild.findViewById(imageViewId);
214        if (v == null) return;
215        final Drawable d = v.getBackground();
216        if (dark) {
217            v.setLayerType(LAYER_TYPE_HARDWARE, INVERT_PAINT);
218            if (d != null) {
219                v.setTag(R.id.doze_saved_filter_tag, d.getColorFilter() != null ? d.getColorFilter()
220                        : NO_COLOR_FILTER);
221                d.setColorFilter(getResources().getColor(R.color.doze_small_icon_background_color),
222                        PorterDuff.Mode.SRC_ATOP);
223                v.setImageAlpha(getResources().getInteger(R.integer.doze_small_icon_alpha));
224            }
225        } else {
226            v.setLayerType(LAYER_TYPE_NONE, null);
227            if (d != null)  {
228                final ColorFilter filter = (ColorFilter) v.getTag(R.id.doze_saved_filter_tag);
229                if (filter != null) {
230                    d.setColorFilter(filter == NO_COLOR_FILTER ? null : filter);
231                    v.setTag(R.id.doze_saved_filter_tag, null);
232                }
233                v.setImageAlpha(0xff);
234            }
235        }
236    }
237
238    private static Paint createInvertPaint() {
239        final Paint p = new Paint();
240        final float[] invert = {
241            -1f,  0f,  0f, 1f, 1f,
242             0f, -1f,  0f, 1f, 1f,
243             0f,  0f, -1f, 1f, 1f,
244             0f,  0f,  0f, 1f, 0f
245        };
246        p.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(invert)));
247        return p;
248    }
249}
250