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