• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.animation.Animator;
18 import android.animation.AnimatorListenerAdapter;
19 import android.animation.ArgbEvaluator;
20 import android.animation.PropertyValuesHolder;
21 import android.animation.ValueAnimator;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.content.res.ColorStateList;
25 import android.content.res.Configuration;
26 import android.content.res.Resources;
27 import android.graphics.drawable.Animatable2;
28 import android.graphics.drawable.Animatable2.AnimationCallback;
29 import android.graphics.drawable.Drawable;
30 import android.service.quicksettings.Tile;
31 import android.util.Log;
32 import android.view.View;
33 import android.widget.ImageView;
34 import android.widget.ImageView.ScaleType;
35 
36 import com.android.settingslib.Utils;
37 import com.android.systemui.R;
38 import com.android.systemui.plugins.qs.QSIconView;
39 import com.android.systemui.plugins.qs.QSTile;
40 import com.android.systemui.plugins.qs.QSTile.State;
41 import com.android.systemui.qs.AlphaControlledSignalTileView.AlphaControlledSlashImageView;
42 
43 import java.util.Objects;
44 
45 public class QSIconViewImpl extends QSIconView {
46 
47     public static final long QS_ANIM_LENGTH = 350;
48 
49     protected final View mIcon;
50     protected int mIconSizePx;
51     private boolean mAnimationEnabled = true;
52     private int mState = -1;
53     private boolean mDisabledByPolicy = false;
54     private int mTint;
55     @Nullable
56     private QSTile.Icon mLastIcon;
57 
58     private ValueAnimator mColorAnimator = new ValueAnimator();
59 
QSIconViewImpl(Context context)60     public QSIconViewImpl(Context context) {
61         super(context);
62 
63         final Resources res = context.getResources();
64         mIconSizePx = res.getDimensionPixelSize(R.dimen.qs_icon_size);
65 
66         mIcon = createIcon();
67         addView(mIcon);
68         mColorAnimator.setDuration(QS_ANIM_LENGTH);
69     }
70 
71     @Override
onConfigurationChanged(Configuration newConfig)72     protected void onConfigurationChanged(Configuration newConfig) {
73         super.onConfigurationChanged(newConfig);
74         mIconSizePx = getContext().getResources().getDimensionPixelSize(R.dimen.qs_icon_size);
75     }
76 
disableAnimation()77     public void disableAnimation() {
78         mAnimationEnabled = false;
79     }
80 
getIconView()81     public View getIconView() {
82         return mIcon;
83     }
84 
85     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)86     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
87         final int w = MeasureSpec.getSize(widthMeasureSpec);
88         final int iconSpec = exactly(mIconSizePx);
89         mIcon.measure(MeasureSpec.makeMeasureSpec(w, getIconMeasureMode()), iconSpec);
90         setMeasuredDimension(w, mIcon.getMeasuredHeight());
91     }
92 
93     @Override
toString()94     public String toString() {
95         final StringBuilder sb = new StringBuilder(getClass().getSimpleName()).append('[');
96         sb.append("state=" + mState);
97         sb.append(", tint=" + mTint);
98         if (mLastIcon != null) sb.append(", lastIcon=" + mLastIcon.toString());
99         sb.append("]");
100         return sb.toString();
101     }
102 
103     @Override
onLayout(boolean changed, int l, int t, int r, int b)104     protected void onLayout(boolean changed, int l, int t, int r, int b) {
105         final int w = getMeasuredWidth();
106         int top = 0;
107         final int iconLeft = (w - mIcon.getMeasuredWidth()) / 2;
108         layout(mIcon, iconLeft, top);
109     }
110 
setIcon(State state, boolean allowAnimations)111     public void setIcon(State state, boolean allowAnimations) {
112         setIcon((ImageView) mIcon, state, allowAnimations);
113     }
114 
updateIcon(ImageView iv, State state, boolean allowAnimations)115     protected void updateIcon(ImageView iv, State state, boolean allowAnimations) {
116         final QSTile.Icon icon = state.iconSupplier != null ? state.iconSupplier.get() : state.icon;
117         if (!Objects.equals(icon, iv.getTag(R.id.qs_icon_tag))
118                 || !Objects.equals(state.slash, iv.getTag(R.id.qs_slash_tag))) {
119             boolean shouldAnimate = allowAnimations && shouldAnimate(iv);
120             mLastIcon = icon;
121             Drawable d = icon != null
122                     ? shouldAnimate ? icon.getDrawable(mContext)
123                     : icon.getInvisibleDrawable(mContext) : null;
124             int padding = icon != null ? icon.getPadding() : 0;
125             if (d != null) {
126                 if (d.getConstantState() != null) {
127                     d = d.getConstantState().newDrawable();
128                 }
129                 d.setAutoMirrored(false);
130                 d.setLayoutDirection(getLayoutDirection());
131             }
132 
133             if (iv instanceof SlashImageView) {
134                 ((SlashImageView) iv).setAnimationEnabled(shouldAnimate);
135                 ((SlashImageView) iv).setState(null, d);
136             } else {
137                 iv.setImageDrawable(d);
138             }
139 
140             iv.setTag(R.id.qs_icon_tag, icon);
141             iv.setTag(R.id.qs_slash_tag, state.slash);
142             iv.setPadding(0, padding, 0, padding);
143             if (d instanceof Animatable2) {
144                 Animatable2 a = (Animatable2) d;
145                 a.start();
146                 if (shouldAnimate) {
147                     if (state.isTransient) {
148                         a.registerAnimationCallback(new AnimationCallback() {
149                             @Override
150                             public void onAnimationEnd(Drawable drawable) {
151                                 a.start();
152                             }
153                         });
154                     }
155                 } else {
156                     // Sends animator to end of animation. Needs to be called after calling start.
157                     a.stop();
158                 }
159             }
160         }
161     }
162 
shouldAnimate(ImageView iv)163     private boolean shouldAnimate(ImageView iv) {
164         return mAnimationEnabled && iv.isShown() && iv.getDrawable() != null;
165     }
166 
setIcon(ImageView iv, QSTile.State state, boolean allowAnimations)167     protected void setIcon(ImageView iv, QSTile.State state, boolean allowAnimations) {
168         if (state.state != mState || state.disabledByPolicy != mDisabledByPolicy) {
169             int color = getColor(state);
170             mState = state.state;
171             mDisabledByPolicy = state.disabledByPolicy;
172             if (mTint != 0 && allowAnimations && shouldAnimate(iv)) {
173                 animateGrayScale(mTint, color, iv, () -> updateIcon(iv, state, allowAnimations));
174             } else {
175                 if (iv instanceof AlphaControlledSlashImageView) {
176                     ((AlphaControlledSlashImageView)iv)
177                             .setFinalImageTintList(ColorStateList.valueOf(color));
178                 } else {
179                     setTint(iv, color);
180                 }
181                 updateIcon(iv, state, allowAnimations);
182             }
183         } else {
184             updateIcon(iv, state, allowAnimations);
185         }
186     }
187 
getColor(QSTile.State state)188     protected int getColor(QSTile.State state) {
189         return getIconColorForState(getContext(), state);
190     }
191 
animateGrayScale(int fromColor, int toColor, ImageView iv, final Runnable endRunnable)192     private void animateGrayScale(int fromColor, int toColor, ImageView iv,
193         final Runnable endRunnable) {
194         if (iv instanceof AlphaControlledSlashImageView) {
195             ((AlphaControlledSlashImageView)iv)
196                     .setFinalImageTintList(ColorStateList.valueOf(toColor));
197         }
198         mColorAnimator.cancel();
199         if (mAnimationEnabled && ValueAnimator.areAnimatorsEnabled()) {
200             PropertyValuesHolder values = PropertyValuesHolder.ofInt("color", fromColor, toColor);
201             values.setEvaluator(ArgbEvaluator.getInstance());
202             mColorAnimator.setValues(values);
203             mColorAnimator.removeAllListeners();
204             mColorAnimator.addUpdateListener(animation -> {
205                 setTint(iv, (int) animation.getAnimatedValue());
206             });
207             mColorAnimator.addListener(new EndRunnableAnimatorListener(endRunnable));
208 
209             mColorAnimator.start();
210         } else {
211 
212             setTint(iv, toColor);
213             endRunnable.run();
214         }
215     }
216 
setTint(ImageView iv, int color)217     public void setTint(ImageView iv, int color) {
218         iv.setImageTintList(ColorStateList.valueOf(color));
219         mTint = color;
220     }
221 
getIconMeasureMode()222     protected int getIconMeasureMode() {
223         return MeasureSpec.EXACTLY;
224     }
225 
createIcon()226     protected View createIcon() {
227         final ImageView icon = new SlashImageView(mContext);
228         icon.setId(android.R.id.icon);
229         icon.setScaleType(ScaleType.FIT_CENTER);
230         return icon;
231     }
232 
exactly(int size)233     protected final int exactly(int size) {
234         return MeasureSpec.makeMeasureSpec(size, MeasureSpec.EXACTLY);
235     }
236 
layout(View child, int left, int top)237     protected final void layout(View child, int left, int top) {
238         child.layout(left, top, left + child.getMeasuredWidth(), top + child.getMeasuredHeight());
239     }
240 
241     /**
242      * Color to tint the tile icon based on state
243      */
getIconColorForState(Context context, QSTile.State state)244     private static int getIconColorForState(Context context, QSTile.State state) {
245         if (state.disabledByPolicy || state.state == Tile.STATE_UNAVAILABLE) {
246             return Utils.getColorAttrDefaultColor(
247                     context, com.android.internal.R.attr.textColorTertiary);
248         } else if (state.state == Tile.STATE_INACTIVE) {
249             return Utils.getColorAttrDefaultColor(context, android.R.attr.textColorPrimary);
250         } else if (state.state == Tile.STATE_ACTIVE) {
251             return Utils.getColorAttrDefaultColor(context,
252                     com.android.internal.R.attr.textColorOnAccent);
253         } else {
254             Log.e("QSIconView", "Invalid state " + state);
255             return 0;
256         }
257     }
258 
259     private static class EndRunnableAnimatorListener extends AnimatorListenerAdapter {
260         private Runnable mRunnable;
261 
EndRunnableAnimatorListener(Runnable endRunnable)262         EndRunnableAnimatorListener(Runnable endRunnable) {
263             super();
264             mRunnable = endRunnable;
265         }
266 
267         @Override
onAnimationCancel(Animator animation)268         public void onAnimationCancel(Animator animation) {
269             super.onAnimationCancel(animation);
270             mRunnable.run();
271         }
272 
273         @Override
onAnimationEnd(Animator animation)274         public void onAnimationEnd(Animator animation) {
275             super.onAnimationEnd(animation);
276             mRunnable.run();
277         }
278     }
279 }
280