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
17package com.android.systemui.charging;
18
19import android.content.Context;
20import android.graphics.Canvas;
21import android.graphics.Paint;
22import android.util.AttributeSet;
23import android.view.View;
24import android.view.animation.Interpolator;
25
26import com.android.settingslib.Utils;
27import com.android.systemui.Interpolators;
28import com.android.systemui.R;
29
30final class WirelessChargingView extends View {
31
32    private Interpolator mInterpolator;
33    private float mPathGone;
34    private float mInterpolatedPathGone;
35    private long mAnimationStartTime;
36    private long mScaleDotsDuration;
37
38    private boolean mFinishedAnimatingDots = false;
39    private int mNumDots;
40
41    private double mAngleOffset;
42    private double mCurrAngleOffset;
43
44    private int mDotsRadiusStart;
45    private int mDotsRadiusEnd;
46    private int mCurrDotRadius;
47
48    private int mMainCircleStartRadius;
49    private int mMainCircleEndRadius;
50    private int mMainCircleCurrentRadius;
51
52    private int mCenterX;
53    private int mCenterY;
54
55    private Paint mPaint;
56    private Context mContext;
57
58    public WirelessChargingView(Context context) {
59        super(context);
60        init(context, null);
61    }
62
63    public WirelessChargingView(Context context, AttributeSet attr) {
64        super(context, attr);
65        init(context, attr);
66    }
67
68    public WirelessChargingView(Context context, AttributeSet attr, int styleAttr) {
69        super(context, attr, styleAttr);
70        init(context, attr);
71    }
72
73    public void init(Context context, AttributeSet attr) {
74        mContext = context;
75
76        mDotsRadiusStart = context.getResources().getDimensionPixelSize(
77                R.dimen.wireless_charging_dots_radius_start);
78        mDotsRadiusEnd = context.getResources().getDimensionPixelSize(
79                R.dimen.wireless_charging_dots_radius_end);
80
81        mMainCircleStartRadius = context.getResources().getDimensionPixelSize(
82                R.dimen.wireless_charging_circle_radius_start);
83        mMainCircleEndRadius = context.getResources().getDimensionPixelSize(
84                R.dimen.wireless_charging_circle_radius_end);
85        mMainCircleCurrentRadius = mMainCircleStartRadius;
86
87        mAngleOffset = context.getResources().getInteger(R.integer.wireless_charging_angle_offset);
88        mScaleDotsDuration = (long) context.getResources().getInteger(
89                R.integer.wireless_charging_scale_dots_duration);
90        mNumDots = context.getResources().getInteger(R.integer.wireless_charging_num_dots);
91
92        setupPaint();
93        mInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
94    }
95
96    private void setupPaint() {
97        mPaint = new Paint();
98        mPaint.setColor(Utils.getColorAttr(mContext, R.attr.wallpaperTextColor));
99    }
100
101    public void setPaintColor(int color) {
102        mPaint.setColor(color);
103    }
104
105    @Override
106    protected void onDraw(final Canvas canvas) {
107        super.onDraw(canvas);
108
109        if (mAnimationStartTime == 0) {
110            mAnimationStartTime = System.currentTimeMillis();
111        }
112
113        updateDrawingParameters();
114        drawCircles(canvas);
115
116        if (!mFinishedAnimatingDots) {
117            invalidate();
118        }
119    }
120
121    /**
122     * Draws a larger circle of radius {@link WirelessChargingView#mMainCircleEndRadius} composed of
123     * {@link WirelessChargingView#mNumDots} smaller circles
124     * @param canvas
125     */
126    private void drawCircles(Canvas canvas) {
127        mCenterX = canvas.getWidth() / 2;
128        mCenterY = canvas.getHeight() / 2;
129
130        // draws mNumDots to compose a larger, main circle
131        for (int circle = 0; circle < mNumDots; circle++) {
132            double angle = ((mCurrAngleOffset) * Math.PI / 180) + (circle * ((2 * Math.PI)
133                    / mNumDots));
134
135            int x = (int) (mCenterX + Math.cos(angle) * (mMainCircleCurrentRadius +
136                    mCurrDotRadius));
137            int y = (int) (mCenterY + Math.sin(angle) * (mMainCircleCurrentRadius +
138                    mCurrDotRadius));
139
140            canvas.drawCircle(x, y, mCurrDotRadius, mPaint);
141        }
142
143        if (mMainCircleCurrentRadius >= mMainCircleEndRadius) {
144            mFinishedAnimatingDots = true;
145        }
146    }
147
148    private void updateDrawingParameters() {
149        long now = System.currentTimeMillis();
150        long timeSinceStart = now - mAnimationStartTime;
151        mPathGone = getPathGone(now);
152        mInterpolatedPathGone = mInterpolator.getInterpolation(mPathGone);
153
154        // Position Dots: update main circle radius (that the dots compose)
155        if (mPathGone < 1.0f) {
156            mMainCircleCurrentRadius = mMainCircleStartRadius + (int) (mInterpolatedPathGone *
157                    (mMainCircleEndRadius - mMainCircleStartRadius));
158        } else {
159            mMainCircleCurrentRadius = mMainCircleEndRadius;
160        }
161
162        // Scale Dots: update dot radius
163        if (timeSinceStart < mScaleDotsDuration) {
164            mCurrDotRadius = mDotsRadiusStart + (int) (mInterpolator.getInterpolation((float)
165                    timeSinceStart / mScaleDotsDuration) * (mDotsRadiusEnd - mDotsRadiusStart));
166        } else {
167            mCurrDotRadius = mDotsRadiusEnd;
168        }
169
170        // Rotation Dot Group: dots rotate from 0 to 20 degrees
171        mCurrAngleOffset = mAngleOffset * mPathGone;
172    }
173
174    /**
175     * @return decimal depicting how far along the creation of the larger circle (of circles) is
176     * For values < 1.0, the larger circle is being drawn
177     * For values > 1.0 the larger circle has been drawn and further animation can occur
178     */
179    private float getPathGone(long now) {
180        return (float) (now - mAnimationStartTime) / (WirelessChargingAnimation.DURATION);
181    }
182}