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.internal.app;
18 
19 import android.animation.ObjectAnimator;
20 import android.animation.TimeAnimator;
21 import android.app.Activity;
22 import android.content.ActivityNotFoundException;
23 import android.content.ContentResolver;
24 import android.content.Intent;
25 import android.content.res.ColorStateList;
26 import android.graphics.Bitmap;
27 import android.graphics.BitmapShader;
28 import android.graphics.Canvas;
29 import android.graphics.ColorFilter;
30 import android.graphics.Matrix;
31 import android.graphics.Paint;
32 import android.graphics.Path;
33 import android.graphics.PixelFormat;
34 import android.graphics.Shader;
35 import android.graphics.drawable.Drawable;
36 import android.os.Bundle;
37 import android.provider.Settings;
38 import android.util.Log;
39 import android.view.HapticFeedbackConstants;
40 import android.view.MotionEvent;
41 import android.view.View;
42 import android.view.ViewGroup;
43 import android.widget.ImageView;
44 
45 import com.android.internal.R;
46 
47 import org.json.JSONObject;
48 
49 /**
50  * @hide
51  */
52 public class PlatLogoActivity extends Activity {
53     ImageView mZeroView, mOneView;
54     BackslashDrawable mBackslash;
55     int mClicks;
56 
57     static final Paint sPaint = new Paint();
58     static {
59         sPaint.setStyle(Paint.Style.STROKE);
60         sPaint.setStrokeWidth(4f);
61         sPaint.setStrokeCap(Paint.Cap.SQUARE);
62     }
63 
64     @Override
onPause()65     protected void onPause() {
66         if (mBackslash != null) {
67             mBackslash.stopAnimating();
68         }
69         mClicks = 0;
70         super.onPause();
71     }
72 
73     @Override
onCreate(Bundle savedInstanceState)74     protected void onCreate(Bundle savedInstanceState) {
75         super.onCreate(savedInstanceState);
76         final float dp = getResources().getDisplayMetrics().density;
77 
78         getWindow().getDecorView().setSystemUiVisibility(
79                 View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
80         getWindow().setNavigationBarColor(0);
81         getWindow().setStatusBarColor(0);
82 
83         getActionBar().hide();
84 
85         setContentView(R.layout.platlogo_layout);
86 
87         mBackslash = new BackslashDrawable((int) (50 * dp));
88 
89         mOneView = findViewById(R.id.one);
90         mOneView.setImageDrawable(new OneDrawable());
91         mZeroView = findViewById(R.id.zero);
92         mZeroView.setImageDrawable(new ZeroDrawable());
93 
94         final ViewGroup root = (ViewGroup) mOneView.getParent();
95         root.setClipChildren(false);
96         root.setBackground(mBackslash);
97         root.getBackground().setAlpha(0x20);
98 
99         View.OnTouchListener tl = new View.OnTouchListener() {
100             float mOffsetX, mOffsetY;
101             long mClickTime;
102             ObjectAnimator mRotAnim;
103             @Override
104             public boolean onTouch(View v, MotionEvent event) {
105                 measureTouchPressure(event);
106                 switch (event.getActionMasked()) {
107                     case MotionEvent.ACTION_DOWN:
108                         v.animate().scaleX(1.1f).scaleY(1.1f);
109                         v.getParent().bringChildToFront(v);
110                         mOffsetX = event.getRawX() - v.getX();
111                         mOffsetY = event.getRawY() - v.getY();
112                         long now = System.currentTimeMillis();
113                         if (now - mClickTime < 350) {
114                             mRotAnim = ObjectAnimator.ofFloat(v, View.ROTATION,
115                                     v.getRotation(), v.getRotation() + 3600);
116                             mRotAnim.setDuration(10000);
117                             mRotAnim.start();
118                             mClickTime = 0;
119                         } else {
120                             mClickTime = now;
121                         }
122                         break;
123                     case MotionEvent.ACTION_MOVE:
124                         v.setX(event.getRawX() - mOffsetX);
125                         v.setY(event.getRawY() - mOffsetY);
126                         v.performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE);
127                         break;
128                     case MotionEvent.ACTION_UP:
129                         v.performClick();
130                         // fall through
131                     case MotionEvent.ACTION_CANCEL:
132                         v.animate().scaleX(1f).scaleY(1f);
133                         if (mRotAnim != null) mRotAnim.cancel();
134                         testOverlap();
135                         break;
136                 }
137                 return true;
138             }
139         };
140 
141         findViewById(R.id.one).setOnTouchListener(tl);
142         findViewById(R.id.zero).setOnTouchListener(tl);
143         findViewById(R.id.text).setOnTouchListener(tl);
144     }
145 
testOverlap()146     private void testOverlap() {
147         final float width = mZeroView.getWidth();
148         final float targetX = mZeroView.getX() + width * .2f;
149         final float targetY = mZeroView.getY() + width * .3f;
150         if (Math.hypot(targetX - mOneView.getX(), targetY - mOneView.getY()) < width * .2f
151                 && Math.abs(mOneView.getRotation() % 360 - 315) < 15) {
152             mOneView.animate().x(mZeroView.getX() + width * .2f);
153             mOneView.animate().y(mZeroView.getY() + width * .3f);
154             mOneView.setRotation(mOneView.getRotation() % 360);
155             mOneView.animate().rotation(315);
156             mOneView.performHapticFeedback(HapticFeedbackConstants.CONFIRM);
157 
158             mBackslash.startAnimating();
159 
160             mClicks++;
161             if (mClicks >= 7) {
162                 launchNextStage();
163             }
164         } else {
165             mBackslash.stopAnimating();
166         }
167     }
168 
launchNextStage()169     private void launchNextStage() {
170         final ContentResolver cr = getContentResolver();
171 
172         if (Settings.System.getLong(cr, "egg_mode" /* Settings.System.EGG_MODE */, 0) == 0) {
173             // For posterity: the moment this user unlocked the easter egg
174             try {
175                 Settings.System.putLong(cr,
176                         "egg_mode", // Settings.System.EGG_MODE,
177                         System.currentTimeMillis());
178             } catch (RuntimeException e) {
179                 Log.e("com.android.internal.app.PlatLogoActivity", "Can't write settings", e);
180             }
181         }
182         try {
183             startActivity(new Intent(Intent.ACTION_MAIN)
184                     .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
185                         | Intent.FLAG_ACTIVITY_CLEAR_TASK)
186                     .addCategory("com.android.internal.category.PLATLOGO"));
187         } catch (ActivityNotFoundException ex) {
188             Log.e("com.android.internal.app.PlatLogoActivity", "No more eggs.");
189         }
190         finish();
191     }
192 
193     static final String TOUCH_STATS = "touch.stats";
194     double mPressureMin = 0, mPressureMax = -1;
195 
measureTouchPressure(MotionEvent event)196     private void measureTouchPressure(MotionEvent event) {
197         final float pressure = event.getPressure();
198         switch (event.getActionMasked()) {
199             case MotionEvent.ACTION_DOWN:
200                 if (mPressureMax < 0) {
201                     mPressureMin = mPressureMax = pressure;
202                 }
203                 break;
204             case MotionEvent.ACTION_MOVE:
205                 if (pressure < mPressureMin) mPressureMin = pressure;
206                 if (pressure > mPressureMax) mPressureMax = pressure;
207                 break;
208         }
209     }
210 
syncTouchPressure()211     private void syncTouchPressure() {
212         try {
213             final String touchDataJson = Settings.System.getString(
214                     getContentResolver(), TOUCH_STATS);
215             final JSONObject touchData = new JSONObject(
216                     touchDataJson != null ? touchDataJson : "{}");
217             if (touchData.has("min")) {
218                 mPressureMin = Math.min(mPressureMin, touchData.getDouble("min"));
219             }
220             if (touchData.has("max")) {
221                 mPressureMax = Math.max(mPressureMax, touchData.getDouble("max"));
222             }
223             if (mPressureMax >= 0) {
224                 touchData.put("min", mPressureMin);
225                 touchData.put("max", mPressureMax);
226                 Settings.System.putString(getContentResolver(), TOUCH_STATS, touchData.toString());
227             }
228         } catch (Exception e) {
229             Log.e("com.android.internal.app.PlatLogoActivity", "Can't write touch settings", e);
230         }
231     }
232 
233     @Override
onStart()234     public void onStart() {
235         super.onStart();
236         syncTouchPressure();
237     }
238 
239     @Override
onStop()240     public void onStop() {
241         syncTouchPressure();
242         super.onStop();
243     }
244 
245     static class ZeroDrawable extends Drawable {
246         int mTintColor;
247 
248         @Override
draw(Canvas canvas)249         public void draw(Canvas canvas) {
250             sPaint.setColor(mTintColor | 0xFF000000);
251 
252             canvas.save();
253             canvas.scale(canvas.getWidth() / 24f, canvas.getHeight() / 24f);
254 
255             canvas.drawCircle(12f, 12f, 10f, sPaint);
256             canvas.restore();
257         }
258 
259         @Override
setAlpha(int alpha)260         public void setAlpha(int alpha) { }
261 
262         @Override
setColorFilter(ColorFilter colorFilter)263         public void setColorFilter(ColorFilter colorFilter) { }
264 
265         @Override
setTintList(ColorStateList tint)266         public void setTintList(ColorStateList tint) {
267             mTintColor = tint.getDefaultColor();
268         }
269 
270         @Override
getOpacity()271         public int getOpacity() {
272             return PixelFormat.TRANSLUCENT;
273         }
274     }
275 
276     static class OneDrawable extends Drawable {
277         int mTintColor;
278 
279         @Override
draw(Canvas canvas)280         public void draw(Canvas canvas) {
281             sPaint.setColor(mTintColor | 0xFF000000);
282 
283             canvas.save();
284             canvas.scale(canvas.getWidth() / 24f, canvas.getHeight() / 24f);
285 
286             final Path p = new Path();
287             p.moveTo(12f, 21.83f);
288             p.rLineTo(0f, -19.67f);
289             p.rLineTo(-5f, 0f);
290             canvas.drawPath(p, sPaint);
291             canvas.restore();
292         }
293 
294         @Override
setAlpha(int alpha)295         public void setAlpha(int alpha) { }
296 
297         @Override
setColorFilter(ColorFilter colorFilter)298         public void setColorFilter(ColorFilter colorFilter) { }
299 
300         @Override
setTintList(ColorStateList tint)301         public void setTintList(ColorStateList tint) {
302             mTintColor = tint.getDefaultColor();
303         }
304 
305         @Override
getOpacity()306         public int getOpacity() {
307             return PixelFormat.TRANSLUCENT;
308         }
309     }
310 
311     private static class BackslashDrawable extends Drawable implements TimeAnimator.TimeListener {
312         Bitmap mTile;
313         Paint mPaint = new Paint();
314         BitmapShader mShader;
315         TimeAnimator mAnimator = new TimeAnimator();
316         Matrix mMatrix = new Matrix();
317 
draw(Canvas canvas)318         public void draw(Canvas canvas) {
319             canvas.drawPaint(mPaint);
320         }
321 
BackslashDrawable(int width)322         BackslashDrawable(int width) {
323             mTile = Bitmap.createBitmap(width, width, Bitmap.Config.ALPHA_8);
324             mAnimator.setTimeListener(this);
325 
326             final Canvas tileCanvas = new Canvas(mTile);
327             final float w = tileCanvas.getWidth();
328             final float h = tileCanvas.getHeight();
329 
330             final Path path = new Path();
331             path.moveTo(0, 0);
332             path.lineTo(w / 2, 0);
333             path.lineTo(w, h / 2);
334             path.lineTo(w, h);
335             path.close();
336 
337             path.moveTo(0, h / 2);
338             path.lineTo(w / 2, h);
339             path.lineTo(0, h);
340             path.close();
341 
342             final Paint slashPaint = new Paint();
343             slashPaint.setAntiAlias(true);
344             slashPaint.setStyle(Paint.Style.FILL);
345             slashPaint.setColor(0xFF000000);
346             tileCanvas.drawPath(path, slashPaint);
347 
348             //mPaint.setColor(0xFF0000FF);
349             mShader = new BitmapShader(mTile, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
350             mPaint.setShader(mShader);
351         }
352 
startAnimating()353         public void startAnimating() {
354             if (!mAnimator.isStarted()) {
355                 mAnimator.start();
356             }
357         }
358 
stopAnimating()359         public void stopAnimating() {
360             if (mAnimator.isStarted()) {
361                 mAnimator.cancel();
362             }
363         }
364 
365         @Override
setAlpha(int alpha)366         public void setAlpha(int alpha) {
367             mPaint.setAlpha(alpha);
368         }
369 
370         @Override
setColorFilter(ColorFilter colorFilter)371         public void setColorFilter(ColorFilter colorFilter) {
372             mPaint.setColorFilter(colorFilter);
373         }
374 
375         @Override
getOpacity()376         public int getOpacity() {
377             return PixelFormat.TRANSLUCENT;
378         }
379 
380         @Override
onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime)381         public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
382             if (mShader != null) {
383                 mMatrix.postTranslate(deltaTime / 4f, 0);
384                 mShader.setLocalMatrix(mMatrix);
385                 invalidateSelf();
386             }
387         }
388     }
389 }
390 
391