NotificationContentView.java revision cab4a60c485872848fc0fa3e65baaeb06d21a7d8
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 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    private static Paint createInvertPaint() {
247        final Paint p = new Paint();
248        final float[] invert = {
249            -1f,  0f,  0f, 1f, 1f,
250             0f, -1f,  0f, 1f, 1f,
251             0f,  0f, -1f, 1f, 1f,
252             0f,  0f,  0f, 1f, 0f
253        };
254        p.setColorFilter(new ColorMatrixColorFilter(new ColorMatrix(invert)));
255        return p;
256    }
257}
258