1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.systemui.qs.tileimpl;
16
17import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState;
18
19import android.animation.ValueAnimator;
20import android.content.Context;
21import android.content.res.ColorStateList;
22import android.content.res.Resources;
23import android.graphics.Color;
24import android.graphics.drawable.Animatable;
25import android.graphics.drawable.Animatable2;
26import android.graphics.drawable.Animatable2.AnimationCallback;
27import android.graphics.drawable.Drawable;
28import android.view.View;
29import android.widget.ImageView;
30import android.widget.ImageView.ScaleType;
31
32import com.android.systemui.R;
33import com.android.systemui.plugins.qs.QSIconView;
34import com.android.systemui.plugins.qs.QSTile;
35import com.android.systemui.plugins.qs.QSTile.State;
36
37import java.util.Objects;
38
39public class QSIconViewImpl extends QSIconView {
40
41    protected final View mIcon;
42    protected final int mIconSizePx;
43    protected final int mTilePaddingBelowIconPx;
44    private boolean mAnimationEnabled = true;
45    private int mState = -1;
46    private int mTint;
47
48    public QSIconViewImpl(Context context) {
49        super(context);
50
51        final Resources res = context.getResources();
52        mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_tile_icon_size);
53        mTilePaddingBelowIconPx =  res.getDimensionPixelSize(R.dimen.qs_tile_padding_below_icon);
54
55        mIcon = createIcon();
56        addView(mIcon);
57    }
58
59    public void disableAnimation() {
60        mAnimationEnabled = false;
61    }
62
63    public View getIconView() {
64        return mIcon;
65    }
66
67    @Override
68    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
69        final int w = MeasureSpec.getSize(widthMeasureSpec);
70        final int iconSpec = exactly(mIconSizePx);
71        mIcon.measure(MeasureSpec.makeMeasureSpec(w, getIconMeasureMode()), iconSpec);
72        setMeasuredDimension(w, mIcon.getMeasuredHeight() + mTilePaddingBelowIconPx);
73    }
74
75    @Override
76    protected void onLayout(boolean changed, int l, int t, int r, int b) {
77        final int w = getMeasuredWidth();
78        int top = 0;
79        final int iconLeft = (w - mIcon.getMeasuredWidth()) / 2;
80        layout(mIcon, iconLeft, top);
81    }
82
83    public void setIcon(QSTile.State state) {
84        setIcon((ImageView) mIcon, state);
85    }
86
87    protected void updateIcon(ImageView iv, State state) {
88        if (!Objects.equals(state.icon, iv.getTag(R.id.qs_icon_tag))) {
89            boolean shouldAnimate = iv.isShown() && mAnimationEnabled
90                    && iv.getDrawable() != null;
91            Drawable d = state.icon != null
92                    ? shouldAnimate ? state.icon.getDrawable(mContext)
93                    : state.icon.getInvisibleDrawable(mContext) : null;
94            int padding = state.icon != null ? state.icon.getPadding() : 0;
95            if (d != null) {
96                d.setAutoMirrored(false);
97            }
98            iv.setImageDrawable(d);
99            iv.setTag(R.id.qs_icon_tag, state.icon);
100            iv.setPadding(0, padding, 0, padding);
101            if (d instanceof Animatable2) {
102                Animatable2 a = (Animatable2) d;
103                a.start();
104                if (state.isTransient) {
105                    a.registerAnimationCallback(new AnimationCallback() {
106                        @Override
107                        public void onAnimationEnd(Drawable drawable) {
108                            a.start();
109                        }
110                    });
111                }
112            }
113        }
114    }
115
116    protected void setIcon(ImageView iv, QSTile.State state) {
117        updateIcon(iv, state);
118        if (state.disabledByPolicy) {
119            iv.setColorFilter(getContext().getColor(R.color.qs_tile_disabled_color));
120        } else {
121            iv.clearColorFilter();
122        }
123        if (state.state != mState) {
124            int color = getColor(state.state);
125            mState = state.state;
126            if (iv.isShown() && mTint != 0) {
127                animateGrayScale(mTint, color, iv);
128                mTint = color;
129            } else {
130                setTint(iv, color);
131                mTint = color;
132            }
133        }
134    }
135
136    protected int getColor(int state) {
137        return getColorForState(getContext(), state);
138    }
139
140    public static void animateGrayScale(int fromColor, int toColor, ImageView iv) {
141        final float fromAlpha = Color.alpha(fromColor);
142        final float toAlpha = Color.alpha(toColor);
143        final float fromChannel = Color.red(fromColor);
144        final float toChannel = Color.red(toColor);
145
146        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
147        anim.setDuration(350);
148
149        anim.addUpdateListener(animation -> {
150            float fraction = animation.getAnimatedFraction();
151            int alpha = (int) (fromAlpha + (toAlpha - fromAlpha) * fraction);
152            int channel = (int) (fromChannel + (toChannel - fromChannel) * fraction);
153
154            setTint(iv, Color.argb(alpha, channel, channel, channel));
155        });
156        anim.start();
157    }
158
159    public static void setTint(ImageView iv, int color) {
160        iv.setImageTintList(ColorStateList.valueOf(color));
161    }
162
163
164    protected int getIconMeasureMode() {
165        return MeasureSpec.EXACTLY;
166    }
167
168    protected View createIcon() {
169        final ImageView icon = new ImageView(mContext);
170        icon.setId(android.R.id.icon);
171        icon.setScaleType(ScaleType.FIT_CENTER);
172        return icon;
173    }
174
175    protected final int exactly(int size) {
176        return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
177    }
178
179    protected final void layout(View child, int left, int top) {
180        child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
181    }
182}
183