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.Animatable2;
25import android.graphics.drawable.Animatable2.AnimationCallback;
26import android.graphics.drawable.Drawable;
27import android.view.View;
28import android.widget.ImageView;
29import android.widget.ImageView.ScaleType;
30
31import com.android.systemui.R;
32import com.android.systemui.plugins.qs.QSIconView;
33import com.android.systemui.plugins.qs.QSTile;
34import com.android.systemui.plugins.qs.QSTile.State;
35
36import com.android.systemui.qs.AlphaControlledSignalTileView.AlphaControlledSlashImageView;
37import java.util.Objects;
38
39public class QSIconViewImpl extends QSIconView {
40
41    public static final long QS_ANIM_LENGTH = 350;
42
43    protected final View mIcon;
44    protected final int mIconSizePx;
45    protected final int mTilePaddingBelowIconPx;
46    private boolean mAnimationEnabled = true;
47    private int mState = -1;
48    private int mTint;
49
50    public QSIconViewImpl(Context context) {
51        super(context);
52
53        final Resources res = context.getResources();
54        mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_tile_icon_size);
55        mTilePaddingBelowIconPx =  res.getDimensionPixelSize(R.dimen.qs_tile_padding_below_icon);
56
57        mIcon = createIcon();
58        addView(mIcon);
59    }
60
61    public void disableAnimation() {
62        mAnimationEnabled = false;
63    }
64
65    public View getIconView() {
66        return mIcon;
67    }
68
69    @Override
70    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
71        final int w = MeasureSpec.getSize(widthMeasureSpec);
72        final int iconSpec = exactly(mIconSizePx);
73        mIcon.measure(MeasureSpec.makeMeasureSpec(w, getIconMeasureMode()), iconSpec);
74        setMeasuredDimension(w, mIcon.getMeasuredHeight() + mTilePaddingBelowIconPx);
75    }
76
77    @Override
78    protected void onLayout(boolean changed, int l, int t, int r, int b) {
79        final int w = getMeasuredWidth();
80        int top = 0;
81        final int iconLeft = (w - mIcon.getMeasuredWidth()) / 2;
82        layout(mIcon, iconLeft, top);
83    }
84
85    public void setIcon(QSTile.State state) {
86        setIcon((ImageView) mIcon, state);
87    }
88
89    protected void updateIcon(ImageView iv, State state) {
90        final QSTile.Icon icon = state.iconSupplier != null ? state.iconSupplier.get() : state.icon;
91        if (!Objects.equals(icon, iv.getTag(R.id.qs_icon_tag))
92                || !Objects.equals(state.slash, iv.getTag(R.id.qs_slash_tag))) {
93            boolean shouldAnimate = iv.isShown() && mAnimationEnabled
94                    && iv.getDrawable() != null;
95            Drawable d = icon != null
96                    ? shouldAnimate ? icon.getDrawable(mContext)
97                    : icon.getInvisibleDrawable(mContext) : null;
98            int padding = icon != null ? icon.getPadding() : 0;
99            if (d != null) {
100                d.setAutoMirrored(false);
101                d.setLayoutDirection(getLayoutDirection());
102            }
103
104            if (iv instanceof SlashImageView) {
105                ((SlashImageView) iv).setAnimationEnabled(shouldAnimate);
106                ((SlashImageView) iv).setState(state.slash, d);
107            } else {
108                iv.setImageDrawable(d);
109            }
110
111            iv.setTag(R.id.qs_icon_tag, icon);
112            iv.setTag(R.id.qs_slash_tag, state.slash);
113            iv.setPadding(0, padding, 0, padding);
114            if (d instanceof Animatable2) {
115                Animatable2 a = (Animatable2) d;
116                a.start();
117                if (state.isTransient) {
118                    a.registerAnimationCallback(new AnimationCallback() {
119                        @Override
120                        public void onAnimationEnd(Drawable drawable) {
121                            a.start();
122                        }
123                    });
124                }
125            }
126        }
127    }
128
129    protected void setIcon(ImageView iv, QSTile.State state) {
130        updateIcon(iv, state);
131        if (state.disabledByPolicy) {
132            iv.setColorFilter(getContext().getColor(R.color.qs_tile_disabled_color));
133        } else {
134            iv.clearColorFilter();
135        }
136        if (state.state != mState) {
137            int color = getColor(state.state);
138            mState = state.state;
139            if (iv.isShown() && mTint != 0) {
140                animateGrayScale(mTint, color, iv);
141                mTint = color;
142            } else {
143                if (iv instanceof AlphaControlledSlashImageView) {
144                    ((AlphaControlledSlashImageView)iv)
145                            .setFinalImageTintList(ColorStateList.valueOf(color));
146                } else {
147                    setTint(iv, color);
148                }
149                mTint = color;
150            }
151        }
152    }
153
154    protected int getColor(int state) {
155        return getColorForState(getContext(), state);
156    }
157
158    public static void animateGrayScale(int fromColor, int toColor, ImageView iv) {
159        if (iv instanceof AlphaControlledSlashImageView) {
160            ((AlphaControlledSlashImageView)iv)
161                    .setFinalImageTintList(ColorStateList.valueOf(toColor));
162        }
163        if (ValueAnimator.areAnimatorsEnabled()) {
164            final float fromAlpha = Color.alpha(fromColor);
165            final float toAlpha = Color.alpha(toColor);
166            final float fromChannel = Color.red(fromColor);
167            final float toChannel = Color.red(toColor);
168
169            ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
170            anim.setDuration(QS_ANIM_LENGTH);
171            anim.addUpdateListener(animation -> {
172                float fraction = animation.getAnimatedFraction();
173                int alpha = (int) (fromAlpha + (toAlpha - fromAlpha) * fraction);
174                int channel = (int) (fromChannel + (toChannel - fromChannel) * fraction);
175
176                setTint(iv, Color.argb(alpha, channel, channel, channel));
177            });
178
179            anim.start();
180        } else {
181            setTint(iv, toColor);
182        }
183    }
184
185    public static void setTint(ImageView iv, int color) {
186        iv.setImageTintList(ColorStateList.valueOf(color));
187    }
188
189
190    protected int getIconMeasureMode() {
191        return MeasureSpec.EXACTLY;
192    }
193
194    protected View createIcon() {
195        final ImageView icon = new SlashImageView(mContext);
196        icon.setId(android.R.id.icon);
197        icon.setScaleType(ScaleType.FIT_CENTER);
198        return icon;
199    }
200
201    protected final int exactly(int size) {
202        return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
203    }
204
205    protected final void layout(View child, int left, int top) {
206        child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
207    }
208}
209