blob: 2a7eae62679510a070d84941da206dd1c12fbc7b [file] [log] [blame]
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -07001/*
Dan Sandlerbebfd4b2020-06-08 20:48:25 -04002 * Copyright (C) 2020 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 Sandlera7613372019-06-26 01:01:26 -040019import android.animation.ObjectAnimator;
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040020import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.app.ActionBar;
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -070023import android.app.Activity;
Dan Sandlerd1f9f532018-05-02 20:01:38 -040024import android.content.ActivityNotFoundException;
25import android.content.ContentResolver;
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040026import android.content.Context;
Dan Sandlerd1f9f532018-05-02 20:01:38 -040027import android.content.Intent;
Dan Sandler2200f862014-10-06 01:04:47 -040028import android.graphics.Canvas;
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040029import android.graphics.Color;
Dan Sandlerdeafeed2015-08-03 17:42:54 -040030import android.graphics.ColorFilter;
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040031import android.graphics.LinearGradient;
Dan Sandler2200f862014-10-06 01:04:47 -040032import android.graphics.Paint;
Dan Sandlera7613372019-06-26 01:01:26 -040033import android.graphics.PixelFormat;
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040034import android.graphics.Rect;
Dan Sandlera7613372019-06-26 01:01:26 -040035import android.graphics.Shader;
Dan Sandler2200f862014-10-06 01:04:47 -040036import android.graphics.drawable.Drawable;
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -070037import android.os.Bundle;
Dan Sandlerd1f9f532018-05-02 20:01:38 -040038import android.provider.Settings;
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040039import android.util.AttributeSet;
Dan Sandler2200f862014-10-06 01:04:47 -040040import android.util.Log;
Dan Sandlera7613372019-06-26 01:01:26 -040041import android.view.HapticFeedbackConstants;
Dan Sandler93cb9002018-02-06 19:39:22 -050042import android.view.MotionEvent;
Daniel Sandler5f839f82011-10-12 01:45:26 -040043import android.view.View;
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040044import android.view.animation.PathInterpolator;
45import android.widget.FrameLayout;
Dan Sandlera7613372019-06-26 01:01:26 -040046import android.widget.ImageView;
47
48import com.android.internal.R;
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -070049
Dan Sandlerd1f9f532018-05-02 20:01:38 -040050import org.json.JSONObject;
51
Dan Sandlera7613372019-06-26 01:01:26 -040052/**
53 * @hide
54 */
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -070055public class PlatLogoActivity extends Activity {
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040056 private static final boolean WRITE_SETTINGS = true;
Dan Sandler1b1edde2016-05-25 23:13:32 -040057
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040058 BigDialView mDialView;
Dan Sandler93cb9002018-02-06 19:39:22 -050059
Dan Sandlera7613372019-06-26 01:01:26 -040060 @Override
61 protected void onPause() {
Dan Sandlera7613372019-06-26 01:01:26 -040062 super.onPause();
Dan Sandler93cb9002018-02-06 19:39:22 -050063 }
Dan Sandlerc89deae2014-05-06 16:47:35 -040064
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -070065 @Override
66 protected void onCreate(Bundle savedInstanceState) {
67 super.onCreate(savedInstanceState);
Dan Sandlera7613372019-06-26 01:01:26 -040068 final float dp = getResources().getDisplayMetrics().density;
Jeff Brownc2346132012-04-13 01:55:38 -070069
Dan Sandlera7613372019-06-26 01:01:26 -040070 getWindow().getDecorView().setSystemUiVisibility(
71 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
72 getWindow().setNavigationBarColor(0);
73 getWindow().setStatusBarColor(0);
Dan Sandler93cb9002018-02-06 19:39:22 -050074
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040075 final ActionBar ab = getActionBar();
76 if (ab != null) ab.hide();
Dan Sandler93cb9002018-02-06 19:39:22 -050077
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040078 mDialView = new BigDialView(this, null);
79 if (Settings.System.getLong(getContentResolver(),
80 "egg_mode" /* Settings.System.EGG_MODE */, 0) == 0) {
81 mDialView.setUnlockTries(3);
Dan Sandlera7613372019-06-26 01:01:26 -040082 }
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040083
84 final FrameLayout layout = new FrameLayout(this);
85 layout.setBackgroundColor(0xFFFF0000);
86 layout.addView(mDialView, FrameLayout.LayoutParams.MATCH_PARENT,
87 FrameLayout.LayoutParams.MATCH_PARENT);
88 setContentView(layout);
Dan Sandler2200f862014-10-06 01:04:47 -040089 }
Dianne Hackborn1ebccf52010-08-15 13:04:34 -070090
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040091 private void launchNextStage(boolean locked) {
Dan Sandlerd1f9f532018-05-02 20:01:38 -040092 final ContentResolver cr = getContentResolver();
93
Dan Sandlera7613372019-06-26 01:01:26 -040094 if (Settings.System.getLong(cr, "egg_mode" /* Settings.System.EGG_MODE */, 0) == 0) {
Dan Sandlerd1f9f532018-05-02 20:01:38 -040095 // For posterity: the moment this user unlocked the easter egg
96 try {
Dan Sandlerbebfd4b2020-06-08 20:48:25 -040097 if (WRITE_SETTINGS) {
98 Settings.System.putLong(cr,
99 "egg_mode", // Settings.System.EGG_MODE,
100 locked ? 0 : System.currentTimeMillis());
101 }
Dan Sandlerd1f9f532018-05-02 20:01:38 -0400102 } catch (RuntimeException e) {
Dan Sandlera7613372019-06-26 01:01:26 -0400103 Log.e("com.android.internal.app.PlatLogoActivity", "Can't write settings", e);
Dan Sandlerd1f9f532018-05-02 20:01:38 -0400104 }
105 }
106 try {
107 startActivity(new Intent(Intent.ACTION_MAIN)
108 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400109 | Intent.FLAG_ACTIVITY_CLEAR_TASK)
Dan Sandlerd1f9f532018-05-02 20:01:38 -0400110 .addCategory("com.android.internal.category.PLATLOGO"));
111 } catch (ActivityNotFoundException ex) {
Dan Sandlera7613372019-06-26 01:01:26 -0400112 Log.e("com.android.internal.app.PlatLogoActivity", "No more eggs.");
Dan Sandlerd1f9f532018-05-02 20:01:38 -0400113 }
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400114 //finish(); // no longer finish upon unlock; it's fun to frob the dial
Dan Sandlerd1f9f532018-05-02 20:01:38 -0400115 }
116
Dan Sandlera7613372019-06-26 01:01:26 -0400117 static final String TOUCH_STATS = "touch.stats";
118 double mPressureMin = 0, mPressureMax = -1;
119
120 private void measureTouchPressure(MotionEvent event) {
121 final float pressure = event.getPressure();
122 switch (event.getActionMasked()) {
123 case MotionEvent.ACTION_DOWN:
124 if (mPressureMax < 0) {
125 mPressureMin = mPressureMax = pressure;
126 }
127 break;
128 case MotionEvent.ACTION_MOVE:
129 if (pressure < mPressureMin) mPressureMin = pressure;
130 if (pressure > mPressureMax) mPressureMax = pressure;
131 break;
132 }
133 }
134
135 private void syncTouchPressure() {
136 try {
137 final String touchDataJson = Settings.System.getString(
138 getContentResolver(), TOUCH_STATS);
139 final JSONObject touchData = new JSONObject(
140 touchDataJson != null ? touchDataJson : "{}");
141 if (touchData.has("min")) {
142 mPressureMin = Math.min(mPressureMin, touchData.getDouble("min"));
143 }
144 if (touchData.has("max")) {
145 mPressureMax = Math.max(mPressureMax, touchData.getDouble("max"));
146 }
147 if (mPressureMax >= 0) {
148 touchData.put("min", mPressureMin);
149 touchData.put("max", mPressureMax);
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400150 if (WRITE_SETTINGS) {
151 Settings.System.putString(getContentResolver(), TOUCH_STATS,
152 touchData.toString());
153 }
Dan Sandlera7613372019-06-26 01:01:26 -0400154 }
155 } catch (Exception e) {
156 Log.e("com.android.internal.app.PlatLogoActivity", "Can't write touch settings", e);
157 }
158 }
159
Dan Sandler2200f862014-10-06 01:04:47 -0400160 @Override
Dan Sandler93cb9002018-02-06 19:39:22 -0500161 public void onStart() {
162 super.onStart();
Dan Sandlera7613372019-06-26 01:01:26 -0400163 syncTouchPressure();
Dan Sandler93cb9002018-02-06 19:39:22 -0500164 }
William Bourke33f577c2014-10-17 11:13:11 -0700165
Dan Sandler93cb9002018-02-06 19:39:22 -0500166 @Override
167 public void onStop() {
Dan Sandlera7613372019-06-26 01:01:26 -0400168 syncTouchPressure();
Dan Sandler93cb9002018-02-06 19:39:22 -0500169 super.onStop();
Dianne Hackborn1ebccf52010-08-15 13:04:34 -0700170 }
Dan Sandlera7613372019-06-26 01:01:26 -0400171
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400172 class BigDialView extends ImageView {
173 private static final int COLOR_GREEN = 0xff3ddc84;
174 private static final int COLOR_BLUE = 0xff4285f4;
175 private static final int COLOR_NAVY = 0xff073042;
176 private static final int COLOR_ORANGE = 0xfff86734;
177 private static final int COLOR_CHARTREUSE = 0xffeff7cf;
178 private static final int COLOR_LIGHTBLUE = 0xffd7effe;
Dan Sandlera7613372019-06-26 01:01:26 -0400179
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400180 private static final int STEPS = 11;
181 private static final float VALUE_CHANGE_MAX = 1f / STEPS;
Dan Sandlera7613372019-06-26 01:01:26 -0400182
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400183 private BigDialDrawable mDialDrawable;
184 private boolean mWasLocked;
Dan Sandlera7613372019-06-26 01:01:26 -0400185
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400186 BigDialView(Context context, @Nullable AttributeSet attrs) {
187 super(context, attrs);
188 init();
189 }
190
191 BigDialView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
192 super(context, attrs, defStyleAttr);
193 init();
194 }
195
196 BigDialView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
197 int defStyleRes) {
198 super(context, attrs, defStyleAttr, defStyleRes);
199 init();
200 }
201
202 private void init() {
203 mDialDrawable = new BigDialDrawable();
204 setImageDrawable(mDialDrawable);
Dan Sandlera7613372019-06-26 01:01:26 -0400205 }
206
207 @Override
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400208 public void onDraw(Canvas c) {
209 super.onDraw(c);
210 }
Dan Sandlera7613372019-06-26 01:01:26 -0400211
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400212 double toPositiveDegrees(double rad) {
213 return (Math.toDegrees(rad) + 360 - 90) % 360;
Dan Sandlera7613372019-06-26 01:01:26 -0400214 }
215
216 @Override
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400217 public boolean onTouchEvent(MotionEvent ev) {
218 switch (ev.getActionMasked()) {
219 case MotionEvent.ACTION_DOWN:
220 mWasLocked = mDialDrawable.isLocked();
221 // pass through
222 case MotionEvent.ACTION_MOVE:
223 float x = ev.getX();
224 float y = ev.getY();
225 float cx = (getLeft() + getRight()) / 2f;
226 float cy = (getTop() + getBottom()) / 2f;
227 float angle = (float) toPositiveDegrees(Math.atan2(x - cx, y - cy));
228 final int oldLevel = mDialDrawable.getUserLevel();
229 mDialDrawable.touchAngle(angle);
230 final int newLevel = mDialDrawable.getUserLevel();
231 if (oldLevel != newLevel) {
232 performHapticFeedback(newLevel == STEPS
233 ? HapticFeedbackConstants.CONFIRM
234 : HapticFeedbackConstants.CLOCK_TICK);
235 }
236 return true;
237 case MotionEvent.ACTION_UP:
238 if (mWasLocked && !mDialDrawable.isLocked()) {
239 launchNextStage(false);
240 }
241 return true;
Dan Sandlera7613372019-06-26 01:01:26 -0400242 }
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400243 return false;
Dan Sandlera7613372019-06-26 01:01:26 -0400244 }
245
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400246 @Override
247 public boolean performClick() {
248 if (mDialDrawable.getUserLevel() < STEPS - 1) {
249 mDialDrawable.setUserLevel(mDialDrawable.getUserLevel() + 1);
250 performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
Dan Sandlera7613372019-06-26 01:01:26 -0400251 }
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400252 return true;
Dan Sandlera7613372019-06-26 01:01:26 -0400253 }
254
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400255 void setUnlockTries(int tries) {
256 mDialDrawable.setUnlockTries(tries);
Dan Sandlera7613372019-06-26 01:01:26 -0400257 }
258
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400259 private class BigDialDrawable extends Drawable {
260 public final int STEPS = 10;
261 private int mUnlockTries = 0;
262 final Paint mPaint = new Paint();
263 final Drawable mEleven;
264 private boolean mNightMode;
265 private float mValue = 0f;
266 float mElevenAnim = 0f;
267 ObjectAnimator mElevenShowAnimator = ObjectAnimator.ofFloat(this, "elevenAnim", 0f,
268 1f).setDuration(300);
269 ObjectAnimator mElevenHideAnimator = ObjectAnimator.ofFloat(this, "elevenAnim", 1f,
270 0f).setDuration(500);
Dan Sandlera7613372019-06-26 01:01:26 -0400271
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400272 BigDialDrawable() {
273 mNightMode = getContext().getResources().getConfiguration().isNightModeActive();
274 mEleven = getContext().getDrawable(R.drawable.ic_number11);
275 mElevenShowAnimator.setInterpolator(new PathInterpolator(0.4f, 0f, 0.2f, 1f));
276 mElevenHideAnimator.setInterpolator(new PathInterpolator(0.8f, 0.2f, 0.6f, 1f));
277 }
Dan Sandlera7613372019-06-26 01:01:26 -0400278
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400279 public void setUnlockTries(int count) {
280 if (mUnlockTries != count) {
281 mUnlockTries = count;
282 setValue(getValue());
283 invalidateSelf();
284 }
285 }
286
287 boolean isLocked() {
288 return mUnlockTries > 0;
289 }
290
291 public void setValue(float v) {
292 // until the dial is "unlocked", you can't turn it all the way to 11
293 final float max = isLocked() ? 1f - 1f / STEPS : 1f;
294 mValue = v < 0f ? 0f : v > max ? max : v;
Dan Sandlera7613372019-06-26 01:01:26 -0400295 invalidateSelf();
296 }
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400297
298 public float getValue() {
299 return mValue;
300 }
301
302 public int getUserLevel() {
303 return Math.round(getValue() * STEPS - 0.25f);
304 }
305
306 public void setUserLevel(int i) {
307 setValue(getValue() + ((float) i) / STEPS);
308 }
309
310 public float getElevenAnim() {
311 return mElevenAnim;
312 }
313
314 public void setElevenAnim(float f) {
315 if (mElevenAnim != f) {
316 mElevenAnim = f;
317 invalidateSelf();
318 }
319 }
320
321 @Override
322 public void draw(@NonNull Canvas canvas) {
323 final Rect bounds = getBounds();
324 final int w = bounds.width();
325 final int h = bounds.height();
326 final float w2 = w / 2f;
327 final float h2 = h / 2f;
328 final float radius = w / 4f;
329
330 canvas.drawColor(mNightMode ? COLOR_NAVY : COLOR_LIGHTBLUE);
331
332 canvas.save();
333 canvas.rotate(45, w2, h2);
334 canvas.clipRect(w2, h2 - radius, Math.min(w, h), h2 + radius);
335 final int gradientColor = mNightMode ? 0x60000020 : (0x10FFFFFF & COLOR_NAVY);
336 mPaint.setShader(
337 new LinearGradient(w2, h2, Math.min(w, h), h2, gradientColor,
338 0x00FFFFFF & gradientColor, Shader.TileMode.CLAMP));
339 mPaint.setColor(Color.BLACK);
340 canvas.drawPaint(mPaint);
341 mPaint.setShader(null);
342 canvas.restore();
343
344 mPaint.setStyle(Paint.Style.FILL);
345 mPaint.setColor(COLOR_GREEN);
346
347 canvas.drawCircle(w2, h2, radius, mPaint);
348
349 mPaint.setColor(mNightMode ? COLOR_LIGHTBLUE : COLOR_NAVY);
350 final float cx = w * 0.85f;
351 for (int i = 0; i < STEPS; i++) {
352 final float f = (float) i / STEPS;
353 canvas.save();
354 final float angle = valueToAngle(f);
355 canvas.rotate(-angle, w2, h2);
356 canvas.drawCircle(cx, h2, (i <= getUserLevel()) ? 20 : 5, mPaint);
357 canvas.restore();
358 }
359
360 if (mElevenAnim > 0f) {
361 final int color = COLOR_ORANGE;
362 final int size2 = (int) ((0.5 + 0.5f * mElevenAnim) * w / 14);
363 final float cx11 = cx + size2 / 4f;
364 mEleven.setBounds((int) cx11 - size2, (int) h2 - size2,
365 (int) cx11 + size2, (int) h2 + size2);
366 final int alpha = 0xFFFFFF | ((int) clamp(0xFF * 2 * mElevenAnim, 0, 0xFF)
367 << 24);
368 mEleven.setTint(alpha & color);
369 mEleven.draw(canvas);
370 }
371
372 // don't want to use the rounded value here since the quantization will be visible
373 final float angle = valueToAngle(mValue);
374
375 // it's easier to draw at far-right and rotate backwards
376 canvas.rotate(-angle, w2, h2);
377 mPaint.setColor(Color.WHITE);
378 final float dimple = w2 / 12f;
379 canvas.drawCircle(w - radius - dimple * 2, h2, dimple, mPaint);
380 }
381
382 float clamp(float x, float a, float b) {
383 return x < a ? a : x > b ? b : x;
384 }
385
386 float angleToValue(float a) {
387 return 1f - clamp(a / (360 - 45), 0f, 1f);
388 }
389
390 // rotation: min is at 4:30, max is at 3:00
391 float valueToAngle(float v) {
392 return (1f - v) * (360 - 45);
393 }
394
395 public void touchAngle(float a) {
396 final int oldUserLevel = getUserLevel();
397 final float newValue = angleToValue(a);
398 // this is how we prevent the knob from snapping from max back to min, or from
399 // jumping around wherever the user presses. The new value must be pretty close
400 // to the
401 // previous one.
402 if (Math.abs(newValue - getValue()) < VALUE_CHANGE_MAX) {
403 setValue(newValue);
404
405 if (isLocked() && oldUserLevel != STEPS - 1 && getUserLevel() == STEPS - 1) {
406 mUnlockTries--;
407 }
408
409 if (!isLocked()) {
410 if (getUserLevel() == STEPS && mElevenAnim != 1f
411 && !mElevenShowAnimator.isRunning()) {
412 mElevenHideAnimator.cancel();
413 mElevenShowAnimator.start();
414 } else if (getUserLevel() != STEPS && mElevenAnim == 1f
415 && !mElevenHideAnimator.isRunning()) {
416 mElevenShowAnimator.cancel();
417 mElevenHideAnimator.start();
418 }
419 }
420 }
421 }
422
423 @Override
424 public void setAlpha(int i) {
425 }
426
427 @Override
428 public void setColorFilter(@Nullable ColorFilter colorFilter) {
429 }
430
431 @Override
432 public int getOpacity() {
433 return PixelFormat.TRANSLUCENT;
434 }
Dan Sandlera7613372019-06-26 01:01:26 -0400435 }
436 }
Dianne Hackborn08d5b8f2010-08-04 11:12:40 -0700437}
Dan Sandlera7613372019-06-26 01:01:26 -0400438
Dan Sandlerbebfd4b2020-06-08 20:48:25 -0400439
440