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