| /* |
| * Copyright (C) 2018 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.app; |
| |
| import android.animation.ObjectAnimator; |
| import android.animation.TimeAnimator; |
| import android.app.Activity; |
| import android.content.ActivityNotFoundException; |
| import android.content.ContentResolver; |
| import android.content.Intent; |
| import android.content.res.ColorStateList; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapShader; |
| import android.graphics.Canvas; |
| import android.graphics.ColorFilter; |
| import android.graphics.Matrix; |
| import android.graphics.Paint; |
| import android.graphics.Path; |
| import android.graphics.PixelFormat; |
| import android.graphics.Shader; |
| import android.graphics.drawable.Drawable; |
| import android.os.Bundle; |
| import android.provider.Settings; |
| import android.util.Log; |
| import android.view.HapticFeedbackConstants; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.ImageView; |
| |
| import com.android.internal.R; |
| |
| import org.json.JSONObject; |
| |
| /** |
| * @hide |
| */ |
| public class PlatLogoActivity extends Activity { |
| ImageView mZeroView, mOneView; |
| BackslashDrawable mBackslash; |
| int mClicks; |
| |
| static final Paint sPaint = new Paint(); |
| static { |
| sPaint.setStyle(Paint.Style.STROKE); |
| sPaint.setStrokeWidth(4f); |
| sPaint.setStrokeCap(Paint.Cap.SQUARE); |
| } |
| |
| @Override |
| protected void onPause() { |
| if (mBackslash != null) { |
| mBackslash.stopAnimating(); |
| } |
| mClicks = 0; |
| super.onPause(); |
| } |
| |
| @Override |
| protected void onCreate(Bundle savedInstanceState) { |
| super.onCreate(savedInstanceState); |
| final float dp = getResources().getDisplayMetrics().density; |
| |
| getWindow().getDecorView().setSystemUiVisibility( |
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); |
| getWindow().setNavigationBarColor(0); |
| getWindow().setStatusBarColor(0); |
| |
| getActionBar().hide(); |
| |
| setContentView(R.layout.platlogo_layout); |
| |
| mBackslash = new BackslashDrawable((int) (50 * dp)); |
| |
| mOneView = findViewById(R.id.one); |
| mOneView.setImageDrawable(new OneDrawable()); |
| mZeroView = findViewById(R.id.zero); |
| mZeroView.setImageDrawable(new ZeroDrawable()); |
| |
| final ViewGroup root = (ViewGroup) mOneView.getParent(); |
| root.setClipChildren(false); |
| root.setBackground(mBackslash); |
| root.getBackground().setAlpha(0x20); |
| |
| View.OnTouchListener tl = new View.OnTouchListener() { |
| float mOffsetX, mOffsetY; |
| long mClickTime; |
| ObjectAnimator mRotAnim; |
| @Override |
| public boolean onTouch(View v, MotionEvent event) { |
| measureTouchPressure(event); |
| switch (event.getActionMasked()) { |
| case MotionEvent.ACTION_DOWN: |
| v.animate().scaleX(1.1f).scaleY(1.1f); |
| v.getParent().bringChildToFront(v); |
| mOffsetX = event.getRawX() - v.getX(); |
| mOffsetY = event.getRawY() - v.getY(); |
| long now = System.currentTimeMillis(); |
| if (now - mClickTime < 350) { |
| mRotAnim = ObjectAnimator.ofFloat(v, View.ROTATION, |
| v.getRotation(), v.getRotation() + 3600); |
| mRotAnim.setDuration(10000); |
| mRotAnim.start(); |
| mClickTime = 0; |
| } else { |
| mClickTime = now; |
| } |
| break; |
| case MotionEvent.ACTION_MOVE: |
| v.setX(event.getRawX() - mOffsetX); |
| v.setY(event.getRawY() - mOffsetY); |
| v.performHapticFeedback(HapticFeedbackConstants.TEXT_HANDLE_MOVE); |
| break; |
| case MotionEvent.ACTION_UP: |
| v.performClick(); |
| // fall through |
| case MotionEvent.ACTION_CANCEL: |
| v.animate().scaleX(1f).scaleY(1f); |
| if (mRotAnim != null) mRotAnim.cancel(); |
| testOverlap(); |
| break; |
| } |
| return true; |
| } |
| }; |
| |
| findViewById(R.id.one).setOnTouchListener(tl); |
| findViewById(R.id.zero).setOnTouchListener(tl); |
| findViewById(R.id.text).setOnTouchListener(tl); |
| } |
| |
| private void testOverlap() { |
| final float width = mZeroView.getWidth(); |
| final float targetX = mZeroView.getX() + width * .2f; |
| final float targetY = mZeroView.getY() + width * .3f; |
| if (Math.hypot(targetX - mOneView.getX(), targetY - mOneView.getY()) < width * .2f |
| && Math.abs(mOneView.getRotation() % 360 - 315) < 15) { |
| mOneView.animate().x(mZeroView.getX() + width * .2f); |
| mOneView.animate().y(mZeroView.getY() + width * .3f); |
| mOneView.setRotation(mOneView.getRotation() % 360); |
| mOneView.animate().rotation(315); |
| mOneView.performHapticFeedback(HapticFeedbackConstants.CONFIRM); |
| |
| mBackslash.startAnimating(); |
| |
| mClicks++; |
| if (mClicks >= 7) { |
| launchNextStage(); |
| } |
| } else { |
| mBackslash.stopAnimating(); |
| } |
| } |
| |
| private void launchNextStage() { |
| final ContentResolver cr = getContentResolver(); |
| |
| if (Settings.System.getLong(cr, "egg_mode" /* Settings.System.EGG_MODE */, 0) == 0) { |
| // For posterity: the moment this user unlocked the easter egg |
| try { |
| Settings.System.putLong(cr, |
| "egg_mode", // Settings.System.EGG_MODE, |
| System.currentTimeMillis()); |
| } catch (RuntimeException e) { |
| Log.e("com.android.internal.app.PlatLogoActivity", "Can't write settings", e); |
| } |
| } |
| try { |
| startActivity(new Intent(Intent.ACTION_MAIN) |
| .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
| | Intent.FLAG_ACTIVITY_CLEAR_TASK) |
| .addCategory("com.android.internal.category.PLATLOGO")); |
| } catch (ActivityNotFoundException ex) { |
| Log.e("com.android.internal.app.PlatLogoActivity", "No more eggs."); |
| } |
| finish(); |
| } |
| |
| static final String TOUCH_STATS = "touch.stats"; |
| double mPressureMin = 0, mPressureMax = -1; |
| |
| private void measureTouchPressure(MotionEvent event) { |
| final float pressure = event.getPressure(); |
| switch (event.getActionMasked()) { |
| case MotionEvent.ACTION_DOWN: |
| if (mPressureMax < 0) { |
| mPressureMin = mPressureMax = pressure; |
| } |
| break; |
| case MotionEvent.ACTION_MOVE: |
| if (pressure < mPressureMin) mPressureMin = pressure; |
| if (pressure > mPressureMax) mPressureMax = pressure; |
| break; |
| } |
| } |
| |
| private void syncTouchPressure() { |
| try { |
| final String touchDataJson = Settings.System.getString( |
| getContentResolver(), TOUCH_STATS); |
| final JSONObject touchData = new JSONObject( |
| touchDataJson != null ? touchDataJson : "{}"); |
| if (touchData.has("min")) { |
| mPressureMin = Math.min(mPressureMin, touchData.getDouble("min")); |
| } |
| if (touchData.has("max")) { |
| mPressureMax = Math.max(mPressureMax, touchData.getDouble("max")); |
| } |
| if (mPressureMax >= 0) { |
| touchData.put("min", mPressureMin); |
| touchData.put("max", mPressureMax); |
| Settings.System.putString(getContentResolver(), TOUCH_STATS, touchData.toString()); |
| } |
| } catch (Exception e) { |
| Log.e("com.android.internal.app.PlatLogoActivity", "Can't write touch settings", e); |
| } |
| } |
| |
| @Override |
| public void onStart() { |
| super.onStart(); |
| syncTouchPressure(); |
| } |
| |
| @Override |
| public void onStop() { |
| syncTouchPressure(); |
| super.onStop(); |
| } |
| |
| static class ZeroDrawable extends Drawable { |
| int mTintColor; |
| |
| @Override |
| public void draw(Canvas canvas) { |
| sPaint.setColor(mTintColor | 0xFF000000); |
| |
| canvas.save(); |
| canvas.scale(canvas.getWidth() / 24f, canvas.getHeight() / 24f); |
| |
| canvas.drawCircle(12f, 12f, 10f, sPaint); |
| canvas.restore(); |
| } |
| |
| @Override |
| public void setAlpha(int alpha) { } |
| |
| @Override |
| public void setColorFilter(ColorFilter colorFilter) { } |
| |
| @Override |
| public void setTintList(ColorStateList tint) { |
| mTintColor = tint.getDefaultColor(); |
| } |
| |
| @Override |
| public int getOpacity() { |
| return PixelFormat.TRANSLUCENT; |
| } |
| } |
| |
| static class OneDrawable extends Drawable { |
| int mTintColor; |
| |
| @Override |
| public void draw(Canvas canvas) { |
| sPaint.setColor(mTintColor | 0xFF000000); |
| |
| canvas.save(); |
| canvas.scale(canvas.getWidth() / 24f, canvas.getHeight() / 24f); |
| |
| final Path p = new Path(); |
| p.moveTo(12f, 21.83f); |
| p.rLineTo(0f, -19.67f); |
| p.rLineTo(-5f, 0f); |
| canvas.drawPath(p, sPaint); |
| canvas.restore(); |
| } |
| |
| @Override |
| public void setAlpha(int alpha) { } |
| |
| @Override |
| public void setColorFilter(ColorFilter colorFilter) { } |
| |
| @Override |
| public void setTintList(ColorStateList tint) { |
| mTintColor = tint.getDefaultColor(); |
| } |
| |
| @Override |
| public int getOpacity() { |
| return PixelFormat.TRANSLUCENT; |
| } |
| } |
| |
| private static class BackslashDrawable extends Drawable implements TimeAnimator.TimeListener { |
| Bitmap mTile; |
| Paint mPaint = new Paint(); |
| BitmapShader mShader; |
| TimeAnimator mAnimator = new TimeAnimator(); |
| Matrix mMatrix = new Matrix(); |
| |
| public void draw(Canvas canvas) { |
| canvas.drawPaint(mPaint); |
| } |
| |
| BackslashDrawable(int width) { |
| mTile = Bitmap.createBitmap(width, width, Bitmap.Config.ALPHA_8); |
| mAnimator.setTimeListener(this); |
| |
| final Canvas tileCanvas = new Canvas(mTile); |
| final float w = tileCanvas.getWidth(); |
| final float h = tileCanvas.getHeight(); |
| |
| final Path path = new Path(); |
| path.moveTo(0, 0); |
| path.lineTo(w / 2, 0); |
| path.lineTo(w, h / 2); |
| path.lineTo(w, h); |
| path.close(); |
| |
| path.moveTo(0, h / 2); |
| path.lineTo(w / 2, h); |
| path.lineTo(0, h); |
| path.close(); |
| |
| final Paint slashPaint = new Paint(); |
| slashPaint.setAntiAlias(true); |
| slashPaint.setStyle(Paint.Style.FILL); |
| slashPaint.setColor(0xFF000000); |
| tileCanvas.drawPath(path, slashPaint); |
| |
| //mPaint.setColor(0xFF0000FF); |
| mShader = new BitmapShader(mTile, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); |
| mPaint.setShader(mShader); |
| } |
| |
| public void startAnimating() { |
| if (!mAnimator.isStarted()) { |
| mAnimator.start(); |
| } |
| } |
| |
| public void stopAnimating() { |
| if (mAnimator.isStarted()) { |
| mAnimator.cancel(); |
| } |
| } |
| |
| @Override |
| public void setAlpha(int alpha) { |
| mPaint.setAlpha(alpha); |
| } |
| |
| @Override |
| public void setColorFilter(ColorFilter colorFilter) { |
| mPaint.setColorFilter(colorFilter); |
| } |
| |
| @Override |
| public int getOpacity() { |
| return PixelFormat.TRANSLUCENT; |
| } |
| |
| @Override |
| public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) { |
| if (mShader != null) { |
| mMatrix.postTranslate(deltaTime / 4f, 0); |
| mShader.setLocalMatrix(mMatrix); |
| invalidateSelf(); |
| } |
| } |
| } |
| } |
| |