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.internal.app;
18
19import android.animation.TimeAnimator;
20import android.app.Activity;
21import android.graphics.Canvas;
22import android.graphics.Color;
23import android.graphics.ColorFilter;
24import android.graphics.Paint;
25import android.graphics.Path;
26import android.graphics.drawable.Drawable;
27import android.os.Bundle;
28import android.util.Log;
29import android.view.MotionEvent;
30import android.view.MotionEvent.PointerCoords;
31import android.view.View;
32import android.widget.FrameLayout;
33
34public class PlatLogoActivity extends Activity {
35    FrameLayout layout;
36    TimeAnimator anim;
37    PBackground bg;
38
39    private class PBackground extends Drawable {
40        private float maxRadius, radius, x, y, dp;
41        private int[] palette;
42        private int darkest;
43        private float offset;
44
45        public PBackground() {
46            randomizePalette();
47        }
48
49        /**
50         * set inner radius of "p" logo
51         */
52        public void setRadius(float r) {
53            this.radius = Math.max(48*dp, r);
54        }
55
56        /**
57         * move the "p"
58         */
59        public void setPosition(float x, float y) {
60            this.x = x;
61            this.y = y;
62        }
63
64        /**
65         * for animating the "p"
66         */
67        public void setOffset(float o) {
68            this.offset = o;
69        }
70
71        /**
72         * rough luminance calculation
73         * https://www.w3.org/TR/AERT/#color-contrast
74         */
75        public float lum(int rgb) {
76            return ((Color.red(rgb) * 299f) + (Color.green(rgb) * 587f) + (Color.blue(rgb) * 114f)) / 1000f;
77        }
78
79        /**
80         * create a random evenly-spaced color palette
81         * guaranteed to contrast!
82         */
83        public void randomizePalette() {
84            final int slots = 2 + (int)(Math.random() * 2);
85            float[] color = new float[] { (float) Math.random() * 360f, 1f, 1f };
86            palette = new int[slots];
87            darkest = 0;
88            for (int i=0; i<slots; i++) {
89                palette[i] = Color.HSVToColor(color);
90                color[0] += 360f/slots;
91                if (lum(palette[i]) < lum(palette[darkest])) darkest = i;
92            }
93
94            final StringBuilder str = new StringBuilder();
95            for (int c : palette) {
96                str.append(String.format("#%08x ", c));
97            }
98            Log.v("PlatLogoActivity", "color palette: " + str);
99        }
100
101        @Override
102        public void draw(Canvas canvas) {
103            if (dp == 0) dp = getResources().getDisplayMetrics().density;
104            final float width = canvas.getWidth();
105            final float height = canvas.getHeight();
106            if (radius == 0) {
107                setPosition(width / 2, height / 2);
108                setRadius(width / 6);
109            }
110            final float inner_w = radius * 0.667f;
111
112            final Paint paint = new Paint();
113            paint.setStrokeCap(Paint.Cap.BUTT);
114            canvas.translate(x, y);
115
116            Path p = new Path();
117            p.moveTo(-radius, height);
118            p.lineTo(-radius, 0);
119            p.arcTo(-radius, -radius, radius, radius, -180, 270, false);
120            p.lineTo(-radius, radius);
121
122            float w = Math.max(canvas.getWidth(), canvas.getHeight())  * 1.414f;
123            paint.setStyle(Paint.Style.FILL);
124
125            int i=0;
126            while (w > radius*2 + inner_w*2) {
127                paint.setColor(0xFF000000 | palette[i % palette.length]);
128                // for a slower but more complete version:
129                // paint.setStrokeWidth(w);
130                // canvas.drawPath(p, paint);
131                canvas.drawOval(-w/2, -w/2, w/2, w/2, paint);
132                w -= inner_w * (1.1f + Math.sin((i/20f + offset) * 3.14159f));
133                i++;
134            }
135
136            // the innermost circle needs to be a constant color to avoid rapid flashing
137            paint.setColor(0xFF000000 | palette[(darkest+1) % palette.length]);
138            canvas.drawOval(-radius, -radius, radius, radius, paint);
139
140            p.reset();
141            p.moveTo(-radius, height);
142            p.lineTo(-radius, 0);
143            p.arcTo(-radius, -radius, radius, radius, -180, 270, false);
144            p.lineTo(-radius + inner_w, radius);
145
146            paint.setStyle(Paint.Style.STROKE);
147            paint.setStrokeWidth(inner_w*2);
148            paint.setColor(palette[darkest]);
149            canvas.drawPath(p, paint);
150            paint.setStrokeWidth(inner_w);
151            paint.setColor(0xFFFFFFFF);
152            canvas.drawPath(p, paint);
153        }
154
155        @Override
156        public void setAlpha(int alpha) {
157
158        }
159
160        @Override
161        public void setColorFilter(ColorFilter colorFilter) {
162
163        }
164
165        @Override
166        public int getOpacity() {
167            return 0;
168        }
169    }
170
171    @Override
172    protected void onCreate(Bundle savedInstanceState) {
173        super.onCreate(savedInstanceState);
174
175        layout = new FrameLayout(this);
176        setContentView(layout);
177
178        bg = new PBackground();
179        layout.setBackground(bg);
180
181        layout.setOnTouchListener(new View.OnTouchListener() {
182            final PointerCoords pc0 = new PointerCoords();
183            final PointerCoords pc1 = new PointerCoords();
184
185            @Override
186            public boolean onTouch(View v, MotionEvent event) {
187                switch (event.getActionMasked()) {
188                    case MotionEvent.ACTION_DOWN:
189                    case MotionEvent.ACTION_MOVE:
190                        if (event.getPointerCount() > 1) {
191                            event.getPointerCoords(0, pc0);
192                            event.getPointerCoords(1, pc1);
193                            bg.setRadius((float) Math.hypot(pc0.x - pc1.x, pc0.y - pc1.y) / 2f);
194                        }
195                        break;
196                }
197                return true;
198            }
199        });
200    }
201
202    @Override
203    public void onStart() {
204        super.onStart();
205
206        bg.randomizePalette();
207
208        anim = new TimeAnimator();
209        anim.setTimeListener(
210                new TimeAnimator.TimeListener() {
211                    @Override
212                    public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
213                        bg.setOffset((float) totalTime / 60000f);
214                        bg.invalidateSelf();
215                    }
216                });
217
218        anim.start();
219    }
220
221    @Override
222    public void onStop() {
223        if (anim != null) {
224            anim.cancel();
225            anim = null;
226        }
227        super.onStop();
228    }
229}
230