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 
15 package com.android.systemui.qs.tileimpl;
16 
17 import static com.android.systemui.qs.tileimpl.QSTileImpl.getColorForState;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.content.Context;
23 import android.content.res.ColorStateList;
24 import android.content.res.Resources;
25 import android.graphics.Color;
26 import android.graphics.drawable.Animatable2;
27 import android.graphics.drawable.Animatable2.AnimationCallback;
28 import android.graphics.drawable.Drawable;
29 import android.view.View;
30 import android.widget.ImageView;
31 import android.widget.ImageView.ScaleType;
32 
33 import com.android.systemui.R;
34 import com.android.systemui.plugins.qs.QSIconView;
35 import com.android.systemui.plugins.qs.QSTile;
36 import com.android.systemui.plugins.qs.QSTile.State;
37 
38 import com.android.systemui.qs.AlphaControlledSignalTileView.AlphaControlledSlashImageView;
39 import java.util.Objects;
40 
41 public 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 
QSIconViewImpl(Context context)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 
disableAnimation()63     public void disableAnimation() {
64         mAnimationEnabled = false;
65     }
66 
getIconView()67     public View getIconView() {
68         return mIcon;
69     }
70 
71     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)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
onLayout(boolean changed, int l, int t, int r, int b)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 
setIcon(QSTile.State state)87     public void setIcon(QSTile.State state) {
88         setIcon((ImageView) mIcon, state);
89     }
90 
updateIcon(ImageView iv, State state)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 
setIcon(ImageView iv, QSTile.State state)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 
getColor(int state)158     protected int getColor(int state) {
159         return getColorForState(getContext(), state);
160     }
161 
animateGrayScale(int fromColor, int toColor, ImageView iv, final Runnable endRunnable)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 
setTint(ImageView iv, int color)196     public static void setTint(ImageView iv, int color) {
197         iv.setImageTintList(ColorStateList.valueOf(color));
198     }
199 
200 
getIconMeasureMode()201     protected int getIconMeasureMode() {
202         return MeasureSpec.EXACTLY;
203     }
204 
createIcon()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 
exactly(int size)212     protected final int exactly(int size) {
213         return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
214     }
215 
layout(View child, int left, int top)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