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