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