1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.systemui.charging; 18 19 import android.animation.AnimatorSet; 20 import android.animation.ObjectAnimator; 21 import android.animation.ValueAnimator; 22 import android.content.Context; 23 import android.graphics.Color; 24 import android.graphics.PointF; 25 import android.util.AttributeSet; 26 import android.util.TypedValue; 27 import android.view.ContextThemeWrapper; 28 import android.view.View; 29 import android.view.animation.PathInterpolator; 30 import android.widget.FrameLayout; 31 import android.widget.ImageView; 32 import android.widget.TextView; 33 34 import com.android.settingslib.Utils; 35 import com.android.systemui.R; 36 import com.android.systemui.animation.Interpolators; 37 import com.android.systemui.statusbar.charging.ChargingRippleView; 38 39 import java.text.NumberFormat; 40 41 /** 42 * @hide 43 */ 44 public class WirelessChargingLayout extends FrameLayout { 45 public static final int UNKNOWN_BATTERY_LEVEL = -1; 46 private static final long RIPPLE_ANIMATION_DURATION = 1500; 47 private static final int SCRIM_COLOR = 0x4C000000; 48 private static final int SCRIM_FADE_DURATION = 300; 49 private ChargingRippleView mRippleView; 50 WirelessChargingLayout(Context context)51 public WirelessChargingLayout(Context context) { 52 super(context); 53 init(context, null, false); 54 } 55 WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel, boolean isDozing)56 public WirelessChargingLayout(Context context, int transmittingBatteryLevel, int batteryLevel, 57 boolean isDozing) { 58 super(context); 59 init(context, null, transmittingBatteryLevel, batteryLevel, isDozing); 60 } 61 WirelessChargingLayout(Context context, AttributeSet attrs)62 public WirelessChargingLayout(Context context, AttributeSet attrs) { 63 super(context, attrs); 64 init(context, attrs, false); 65 } 66 init(Context c, AttributeSet attrs, boolean isDozing)67 private void init(Context c, AttributeSet attrs, boolean isDozing) { 68 init(c, attrs, -1, -1, false); 69 } 70 init(Context context, AttributeSet attrs, int transmittingBatteryLevel, int batteryLevel, boolean isDozing)71 private void init(Context context, AttributeSet attrs, int transmittingBatteryLevel, 72 int batteryLevel, boolean isDozing) { 73 final boolean showTransmittingBatteryLevel = 74 (transmittingBatteryLevel != UNKNOWN_BATTERY_LEVEL); 75 76 // set style based on background 77 int style = R.style.ChargingAnim_WallpaperBackground; 78 if (isDozing) { 79 style = R.style.ChargingAnim_DarkBackground; 80 } 81 82 inflate(new ContextThemeWrapper(context, style), R.layout.wireless_charging_layout, this); 83 84 // amount of battery: 85 final TextView percentage = findViewById(R.id.wireless_charging_percentage); 86 87 if (batteryLevel != UNKNOWN_BATTERY_LEVEL) { 88 percentage.setText(NumberFormat.getPercentInstance().format(batteryLevel / 100f)); 89 percentage.setAlpha(0); 90 } 91 92 final long chargingAnimationFadeStartOffset = context.getResources().getInteger( 93 R.integer.wireless_charging_fade_offset); 94 final long chargingAnimationFadeDuration = context.getResources().getInteger( 95 R.integer.wireless_charging_fade_duration); 96 final float batteryLevelTextSizeStart = context.getResources().getFloat( 97 R.dimen.wireless_charging_anim_battery_level_text_size_start); 98 final float batteryLevelTextSizeEnd = context.getResources().getFloat( 99 R.dimen.wireless_charging_anim_battery_level_text_size_end) * ( 100 showTransmittingBatteryLevel ? 0.75f : 1.0f); 101 102 // Animation Scale: battery percentage text scales from 0% to 100% 103 ValueAnimator textSizeAnimator = ObjectAnimator.ofFloat(percentage, "textSize", 104 batteryLevelTextSizeStart, batteryLevelTextSizeEnd); 105 textSizeAnimator.setInterpolator(new PathInterpolator(0, 0, 0, 1)); 106 textSizeAnimator.setDuration(context.getResources().getInteger( 107 R.integer.wireless_charging_battery_level_text_scale_animation_duration)); 108 109 // Animation Opacity: battery percentage text transitions from 0 to 1 opacity 110 ValueAnimator textOpacityAnimator = ObjectAnimator.ofFloat(percentage, "alpha", 0, 1); 111 textOpacityAnimator.setInterpolator(Interpolators.LINEAR); 112 textOpacityAnimator.setDuration(context.getResources().getInteger( 113 R.integer.wireless_charging_battery_level_text_opacity_duration)); 114 textOpacityAnimator.setStartDelay(context.getResources().getInteger( 115 R.integer.wireless_charging_anim_opacity_offset)); 116 117 // Animation Opacity: battery percentage text fades from 1 to 0 opacity 118 ValueAnimator textFadeAnimator = ObjectAnimator.ofFloat(percentage, "alpha", 1, 0); 119 textFadeAnimator.setDuration(chargingAnimationFadeDuration); 120 textFadeAnimator.setInterpolator(Interpolators.LINEAR); 121 textFadeAnimator.setStartDelay(chargingAnimationFadeStartOffset); 122 123 // play all animations together 124 AnimatorSet animatorSet = new AnimatorSet(); 125 animatorSet.playTogether(textSizeAnimator, textOpacityAnimator, textFadeAnimator); 126 127 ValueAnimator scrimFadeInAnimator = ObjectAnimator.ofArgb(this, 128 "backgroundColor", Color.TRANSPARENT, SCRIM_COLOR); 129 scrimFadeInAnimator.setDuration(SCRIM_FADE_DURATION); 130 scrimFadeInAnimator.setInterpolator(Interpolators.LINEAR); 131 ValueAnimator scrimFadeOutAnimator = ObjectAnimator.ofArgb(this, 132 "backgroundColor", SCRIM_COLOR, Color.TRANSPARENT); 133 scrimFadeOutAnimator.setDuration(SCRIM_FADE_DURATION); 134 scrimFadeOutAnimator.setInterpolator(Interpolators.LINEAR); 135 scrimFadeOutAnimator.setStartDelay(RIPPLE_ANIMATION_DURATION - SCRIM_FADE_DURATION); 136 AnimatorSet animatorSetScrim = new AnimatorSet(); 137 animatorSetScrim.playTogether(scrimFadeInAnimator, scrimFadeOutAnimator); 138 animatorSetScrim.start(); 139 140 mRippleView = findViewById(R.id.wireless_charging_ripple); 141 OnAttachStateChangeListener listener = new OnAttachStateChangeListener() { 142 @Override 143 public void onViewAttachedToWindow(View view) { 144 mRippleView.setDuration(RIPPLE_ANIMATION_DURATION); 145 mRippleView.startRipple(); 146 mRippleView.removeOnAttachStateChangeListener(this); 147 } 148 149 @Override 150 public void onViewDetachedFromWindow(View view) {} 151 }; 152 mRippleView.addOnAttachStateChangeListener(listener); 153 154 if (!showTransmittingBatteryLevel) { 155 animatorSet.start(); 156 return; 157 } 158 159 // amount of transmitting battery: 160 final TextView transmittingPercentage = findViewById( 161 R.id.reverse_wireless_charging_percentage); 162 transmittingPercentage.setVisibility(VISIBLE); 163 transmittingPercentage.setText( 164 NumberFormat.getPercentInstance().format(transmittingBatteryLevel / 100f)); 165 transmittingPercentage.setAlpha(0); 166 167 // Animation Scale: transmitting battery percentage text scales from 0% to 100% 168 ValueAnimator textSizeAnimatorTransmitting = ObjectAnimator.ofFloat(transmittingPercentage, 169 "textSize", batteryLevelTextSizeStart, batteryLevelTextSizeEnd); 170 textSizeAnimatorTransmitting.setInterpolator(new PathInterpolator(0, 0, 0, 1)); 171 textSizeAnimatorTransmitting.setDuration(context.getResources().getInteger( 172 R.integer.wireless_charging_battery_level_text_scale_animation_duration)); 173 174 // Animation Opacity: transmitting battery percentage text transitions from 0 to 1 opacity 175 ValueAnimator textOpacityAnimatorTransmitting = ObjectAnimator.ofFloat( 176 transmittingPercentage, "alpha", 0, 1); 177 textOpacityAnimatorTransmitting.setInterpolator(Interpolators.LINEAR); 178 textOpacityAnimatorTransmitting.setDuration(context.getResources().getInteger( 179 R.integer.wireless_charging_battery_level_text_opacity_duration)); 180 textOpacityAnimatorTransmitting.setStartDelay( 181 context.getResources().getInteger(R.integer.wireless_charging_anim_opacity_offset)); 182 183 // Animation Opacity: transmitting battery percentage text fades from 1 to 0 opacity 184 ValueAnimator textFadeAnimatorTransmitting = ObjectAnimator.ofFloat(transmittingPercentage, 185 "alpha", 1, 0); 186 textFadeAnimatorTransmitting.setDuration(chargingAnimationFadeDuration); 187 textFadeAnimatorTransmitting.setInterpolator(Interpolators.LINEAR); 188 textFadeAnimatorTransmitting.setStartDelay(chargingAnimationFadeStartOffset); 189 190 // play all animations together 191 AnimatorSet animatorSetTransmitting = new AnimatorSet(); 192 animatorSetTransmitting.playTogether(textSizeAnimatorTransmitting, 193 textOpacityAnimatorTransmitting, textFadeAnimatorTransmitting); 194 195 // transmitting battery icon 196 final ImageView chargingViewIcon = findViewById(R.id.reverse_wireless_charging_icon); 197 chargingViewIcon.setVisibility(VISIBLE); 198 final int padding = Math.round( 199 TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, batteryLevelTextSizeEnd, 200 getResources().getDisplayMetrics())); 201 chargingViewIcon.setPadding(padding, 0, padding, 0); 202 203 // Animation Opacity: transmitting battery icon transitions from 0 to 1 opacity 204 ValueAnimator textOpacityAnimatorIcon = ObjectAnimator.ofFloat(chargingViewIcon, "alpha", 0, 205 1); 206 textOpacityAnimatorIcon.setInterpolator(Interpolators.LINEAR); 207 textOpacityAnimatorIcon.setDuration(context.getResources().getInteger( 208 R.integer.wireless_charging_battery_level_text_opacity_duration)); 209 textOpacityAnimatorIcon.setStartDelay( 210 context.getResources().getInteger(R.integer.wireless_charging_anim_opacity_offset)); 211 212 // Animation Opacity: transmitting battery icon fades from 1 to 0 opacity 213 ValueAnimator textFadeAnimatorIcon = ObjectAnimator.ofFloat(chargingViewIcon, "alpha", 1, 214 0); 215 textFadeAnimatorIcon.setDuration(chargingAnimationFadeDuration); 216 textFadeAnimatorIcon.setInterpolator(Interpolators.LINEAR); 217 textFadeAnimatorIcon.setStartDelay(chargingAnimationFadeStartOffset); 218 219 // play all animations together 220 AnimatorSet animatorSetIcon = new AnimatorSet(); 221 animatorSetIcon.playTogether(textOpacityAnimatorIcon, textFadeAnimatorIcon); 222 223 animatorSet.start(); 224 animatorSetTransmitting.start(); 225 animatorSetIcon.start(); 226 } 227 228 @Override onLayout(boolean changed, int left, int top, int right, int bottom)229 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 230 if (mRippleView != null) { 231 int width = getMeasuredWidth(); 232 int height = getMeasuredHeight(); 233 mRippleView.setColor( 234 Utils.getColorAttr(mRippleView.getContext(), 235 android.R.attr.colorAccent).getDefaultColor()); 236 mRippleView.setOrigin(new PointF(width / 2, height / 2)); 237 mRippleView.setRadius(Math.max(width, height) * 0.5f); 238 } 239 240 super.onLayout(changed, left, top, right, bottom); 241 } 242 } 243