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