blob: 57785443919ed612862eb82e58f891f9aa23f056 [file] [log] [blame]
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -07001/*
Dan Sandler93cb9002018-02-06 19:39:22 -05002 * Copyright (C) 2018 The Android Open Source Project
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -07003 *
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
Dan Sandler93cb9002018-02-06 19:39:22 -050019import android.animation.TimeAnimator;
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -070020import android.app.Activity;
Dan Sandler45f17c52018-05-02 20:01:38 -040021import android.content.ActivityNotFoundException;
22import android.content.ContentResolver;
23import android.content.Intent;
Dan Sandler2200f862014-10-06 01:04:47 -040024import android.graphics.Canvas;
Dan Sandlerdeafeed2015-08-03 17:42:54 -040025import android.graphics.Color;
26import android.graphics.ColorFilter;
Dan Sandler2200f862014-10-06 01:04:47 -040027import android.graphics.Paint;
28import android.graphics.Path;
29import android.graphics.drawable.Drawable;
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -070030import android.os.Bundle;
Dan Sandler45f17c52018-05-02 20:01:38 -040031import android.provider.Settings;
Dan Sandler2200f862014-10-06 01:04:47 -040032import android.util.Log;
Dan Sandler93cb9002018-02-06 19:39:22 -050033import android.view.MotionEvent;
34import android.view.MotionEvent.PointerCoords;
Daniel Sandler5f839f82011-10-12 01:45:26 -040035import android.view.View;
Daniel Sandler06c0e402013-08-08 10:40:34 -040036import android.widget.FrameLayout;
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -070037
Dan Sandler45f17c52018-05-02 20:01:38 -040038import org.json.JSONObject;
39
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -070040public class PlatLogoActivity extends Activity {
Dan Sandler93cb9002018-02-06 19:39:22 -050041 FrameLayout layout;
42 TimeAnimator anim;
43 PBackground bg;
Dan Sandler1b1edde2016-05-25 23:13:32 -040044
Dan Sandler93cb9002018-02-06 19:39:22 -050045 private class PBackground extends Drawable {
46 private float maxRadius, radius, x, y, dp;
47 private int[] palette;
48 private int darkest;
49 private float offset;
50
51 public PBackground() {
52 randomizePalette();
53 }
54
55 /**
56 * set inner radius of "p" logo
57 */
58 public void setRadius(float r) {
59 this.radius = Math.max(48*dp, r);
60 }
61
62 /**
63 * move the "p"
64 */
65 public void setPosition(float x, float y) {
66 this.x = x;
67 this.y = y;
68 }
69
70 /**
71 * for animating the "p"
72 */
73 public void setOffset(float o) {
74 this.offset = o;
75 }
76
77 /**
78 * rough luminance calculation
79 * https://www.w3.org/TR/AERT/#color-contrast
80 */
81 public float lum(int rgb) {
82 return ((Color.red(rgb) * 299f) + (Color.green(rgb) * 587f) + (Color.blue(rgb) * 114f)) / 1000f;
83 }
84
85 /**
86 * create a random evenly-spaced color palette
87 * guaranteed to contrast!
88 */
89 public void randomizePalette() {
90 final int slots = 2 + (int)(Math.random() * 2);
91 float[] color = new float[] { (float) Math.random() * 360f, 1f, 1f };
92 palette = new int[slots];
93 darkest = 0;
94 for (int i=0; i<slots; i++) {
95 palette[i] = Color.HSVToColor(color);
Dan Sandler45f17c52018-05-02 20:01:38 -040096 color[0] = (color[0] + 360f/slots) % 360f;
Dan Sandler93cb9002018-02-06 19:39:22 -050097 if (lum(palette[i]) < lum(palette[darkest])) darkest = i;
98 }
99
100 final StringBuilder str = new StringBuilder();
101 for (int c : palette) {
102 str.append(String.format("#%08x ", c));
103 }
104 Log.v("PlatLogoActivity", "color palette: " + str);
105 }
106
107 @Override
108 public void draw(Canvas canvas) {
109 if (dp == 0) dp = getResources().getDisplayMetrics().density;
110 final float width = canvas.getWidth();
111 final float height = canvas.getHeight();
112 if (radius == 0) {
113 setPosition(width / 2, height / 2);
114 setRadius(width / 6);
115 }
116 final float inner_w = radius * 0.667f;
117
118 final Paint paint = new Paint();
119 paint.setStrokeCap(Paint.Cap.BUTT);
120 canvas.translate(x, y);
121
122 Path p = new Path();
123 p.moveTo(-radius, height);
124 p.lineTo(-radius, 0);
125 p.arcTo(-radius, -radius, radius, radius, -180, 270, false);
126 p.lineTo(-radius, radius);
127
128 float w = Math.max(canvas.getWidth(), canvas.getHeight()) * 1.414f;
129 paint.setStyle(Paint.Style.FILL);
130
131 int i=0;
132 while (w > radius*2 + inner_w*2) {
133 paint.setColor(0xFF000000 | palette[i % palette.length]);
134 // for a slower but more complete version:
135 // paint.setStrokeWidth(w);
136 // canvas.drawPath(p, paint);
137 canvas.drawOval(-w/2, -w/2, w/2, w/2, paint);
138 w -= inner_w * (1.1f + Math.sin((i/20f + offset) * 3.14159f));
139 i++;
140 }
141
142 // the innermost circle needs to be a constant color to avoid rapid flashing
143 paint.setColor(0xFF000000 | palette[(darkest+1) % palette.length]);
144 canvas.drawOval(-radius, -radius, radius, radius, paint);
145
146 p.reset();
147 p.moveTo(-radius, height);
148 p.lineTo(-radius, 0);
149 p.arcTo(-radius, -radius, radius, radius, -180, 270, false);
150 p.lineTo(-radius + inner_w, radius);
151
152 paint.setStyle(Paint.Style.STROKE);
153 paint.setStrokeWidth(inner_w*2);
154 paint.setColor(palette[darkest]);
155 canvas.drawPath(p, paint);
156 paint.setStrokeWidth(inner_w);
157 paint.setColor(0xFFFFFFFF);
158 canvas.drawPath(p, paint);
159 }
160
161 @Override
162 public void setAlpha(int alpha) {
163
164 }
165
166 @Override
167 public void setColorFilter(ColorFilter colorFilter) {
168
169 }
170
171 @Override
172 public int getOpacity() {
173 return 0;
174 }
175 }
Dan Sandlerc89deae2014-05-06 16:47:35 -0400176
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -0700177 @Override
178 protected void onCreate(Bundle savedInstanceState) {
179 super.onCreate(savedInstanceState);
Jeff Brownc2346132012-04-13 01:55:38 -0700180
Dan Sandler93cb9002018-02-06 19:39:22 -0500181 layout = new FrameLayout(this);
182 setContentView(layout);
183
184 bg = new PBackground();
185 layout.setBackground(bg);
186
Dan Sandler45f17c52018-05-02 20:01:38 -0400187 final ContentResolver cr = getContentResolver();
188
Dan Sandler93cb9002018-02-06 19:39:22 -0500189 layout.setOnTouchListener(new View.OnTouchListener() {
Dan Sandler45f17c52018-05-02 20:01:38 -0400190 final String TOUCH_STATS = "touch.stats";
191
Dan Sandler93cb9002018-02-06 19:39:22 -0500192 final PointerCoords pc0 = new PointerCoords();
193 final PointerCoords pc1 = new PointerCoords();
194
Dan Sandler45f17c52018-05-02 20:01:38 -0400195 double pressure_min, pressure_max;
196 int maxPointers;
197 int tapCount;
198
Dan Sandler93cb9002018-02-06 19:39:22 -0500199 @Override
200 public boolean onTouch(View v, MotionEvent event) {
Dan Sandler45f17c52018-05-02 20:01:38 -0400201 final float pressure = event.getPressure();
Dan Sandler93cb9002018-02-06 19:39:22 -0500202 switch (event.getActionMasked()) {
203 case MotionEvent.ACTION_DOWN:
Dan Sandler45f17c52018-05-02 20:01:38 -0400204 pressure_min = pressure_max = pressure;
205 // fall through
Dan Sandler93cb9002018-02-06 19:39:22 -0500206 case MotionEvent.ACTION_MOVE:
Dan Sandler45f17c52018-05-02 20:01:38 -0400207 if (pressure < pressure_min) pressure_min = pressure;
208 if (pressure > pressure_max) pressure_max = pressure;
209 final int pc = event.getPointerCount();
210 if (pc > maxPointers) maxPointers = pc;
211 if (pc > 1) {
Dan Sandler93cb9002018-02-06 19:39:22 -0500212 event.getPointerCoords(0, pc0);
213 event.getPointerCoords(1, pc1);
214 bg.setRadius((float) Math.hypot(pc0.x - pc1.x, pc0.y - pc1.y) / 2f);
215 }
216 break;
Dan Sandler45f17c52018-05-02 20:01:38 -0400217 case MotionEvent.ACTION_CANCEL:
218 case MotionEvent.ACTION_UP:
219 try {
220 final String touchDataJson = Settings.System.getString(cr, TOUCH_STATS);
221 final JSONObject touchData = new JSONObject(
222 touchDataJson != null ? touchDataJson : "{}");
223 if (touchData.has("min")) {
224 pressure_min = Math.min(pressure_min, touchData.getDouble("min"));
225 }
226 if (touchData.has("max")) {
227 pressure_max = Math.max(pressure_max, touchData.getDouble("max"));
228 }
229 touchData.put("min", pressure_min);
230 touchData.put("max", pressure_max);
231 Settings.System.putString(cr, TOUCH_STATS, touchData.toString());
232 } catch (Exception e) {
233 Log.e("PlatLogoActivity", "Can't write touch settings", e);
234 }
235
236 if (maxPointers == 1) {
237 tapCount ++;
238 if (tapCount < 7) {
239 bg.randomizePalette();
240 } else {
241 launchNextStage();
242 }
243 } else {
244 tapCount = 0;
245 }
246 maxPointers = 0;
247 break;
Dan Sandler93cb9002018-02-06 19:39:22 -0500248 }
249 return true;
250 }
251 });
Dan Sandler2200f862014-10-06 01:04:47 -0400252 }
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700253
Dan Sandler45f17c52018-05-02 20:01:38 -0400254 private void launchNextStage() {
255 final ContentResolver cr = getContentResolver();
256
257 if (Settings.System.getLong(cr, Settings.System.EGG_MODE, 0) == 0) {
258 // For posterity: the moment this user unlocked the easter egg
259 try {
260 Settings.System.putLong(cr,
261 Settings.System.EGG_MODE,
262 System.currentTimeMillis());
263 } catch (RuntimeException e) {
264 Log.e("PlatLogoActivity", "Can't write settings", e);
265 }
266 }
267 try {
268 startActivity(new Intent(Intent.ACTION_MAIN)
269 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
270 | Intent.FLAG_ACTIVITY_CLEAR_TASK)
271 .addCategory("com.android.internal.category.PLATLOGO"));
272 } catch (ActivityNotFoundException ex) {
273 Log.e("PlatLogoActivity", "No more eggs.");
274 }
275 finish();
276 }
277
Dan Sandler2200f862014-10-06 01:04:47 -0400278 @Override
Dan Sandler93cb9002018-02-06 19:39:22 -0500279 public void onStart() {
280 super.onStart();
Dan Sandler2200f862014-10-06 01:04:47 -0400281
Dan Sandler93cb9002018-02-06 19:39:22 -0500282 bg.randomizePalette();
Dan Sandlerdeafeed2015-08-03 17:42:54 -0400283
Dan Sandler93cb9002018-02-06 19:39:22 -0500284 anim = new TimeAnimator();
285 anim.setTimeListener(
286 new TimeAnimator.TimeListener() {
Dan Sandler696f6a32015-05-15 15:08:34 -0400287 @Override
Dan Sandler93cb9002018-02-06 19:39:22 -0500288 public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
289 bg.setOffset((float) totalTime / 60000f);
290 bg.invalidateSelf();
Dan Sandler696f6a32015-05-15 15:08:34 -0400291 }
292 });
Dan Sandler2200f862014-10-06 01:04:47 -0400293
Dan Sandler93cb9002018-02-06 19:39:22 -0500294 anim.start();
295 }
William Bourke33f577c2014-10-17 11:13:11 -0700296
Dan Sandler93cb9002018-02-06 19:39:22 -0500297 @Override
298 public void onStop() {
299 if (anim != null) {
300 anim.cancel();
301 anim = null;
302 }
303 super.onStop();
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700304 }
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -0700305}