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