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