Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a7613376 authored by Dan Sandler's avatar Dan Sandler
Browse files

Q card.

(Goodbye, hypno-P and your '90s tech magazine color palette.)

Bug: 123903304
Test: adb shell am start -n android/com.android.internal.app.PlatLogoActivity
Test: adb shell am start -c com.android.internal.category.PLATLOGO -a android.intent.action.MAIN
Test: adb shell am start -n com.android.egg/.paint.PaintActivity # still works
Change-Id: I4865024a14b6a78e7a043c56d2330b5f9dd214c6
parent 99367a7b
Loading
Loading
Loading
Loading
+295 −210
Original line number Diff line number Diff line
@@ -16,252 +16,167 @@

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.Color;
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.MotionEvent.PointerCoords;
import android.view.View;
import android.widget.FrameLayout;
import android.view.ViewGroup;
import android.widget.ImageView;

import org.json.JSONObject;

public class PlatLogoActivity extends Activity {
    FrameLayout layout;
    TimeAnimator anim;
    PBackground bg;

    private class PBackground extends Drawable {
        private float maxRadius, radius, x, y, dp;
        private int[] palette;
        private int darkest;
        private float offset;

        public PBackground() {
            randomizePalette();
        }
import com.android.internal.R;

        /**
         * set inner radius of "p" logo
         */
        public void setRadius(float r) {
            this.radius = Math.max(48*dp, r);
        }

        /**
         * move the "p"
         */
        public void setPosition(float x, float y) {
            this.x = x;
            this.y = y;
        }

        /**
         * for animating the "p"
         */
        public void setOffset(float o) {
            this.offset = o;
        }

        /**
         * rough luminance calculation
         * https://www.w3.org/TR/AERT/#color-contrast
         */
        public float lum(int rgb) {
            return ((Color.red(rgb) * 299f) + (Color.green(rgb) * 587f) + (Color.blue(rgb) * 114f)) / 1000f;
        }
import org.json.JSONObject;

/**
         * create a random evenly-spaced color palette
         * guaranteed to contrast!
 * @hide
 */
        public void randomizePalette() {
            final int slots = 2 + (int)(Math.random() * 2);
            float[] color = new float[] { (float) Math.random() * 360f, 1f, 1f };
            palette = new int[slots];
            darkest = 0;
            for (int i=0; i<slots; i++) {
                palette[i] = Color.HSVToColor(color);
                color[0] = (color[0] + 360f/slots) % 360f;
                if (lum(palette[i]) < lum(palette[darkest])) darkest = i;
            }

            final StringBuilder str = new StringBuilder();
            for (int c : palette) {
                str.append(String.format("#%08x ", c));
            }
            Log.v("PlatLogoActivity", "color palette: " + str);
        }

        @Override
        public void draw(Canvas canvas) {
            if (dp == 0) dp = getResources().getDisplayMetrics().density;
            final float width = canvas.getWidth();
            final float height = canvas.getHeight();
            if (radius == 0) {
                setPosition(width / 2, height / 2);
                setRadius(width / 6);
            }
            final float inner_w = radius * 0.667f;

            final Paint paint = new Paint();
            paint.setStrokeCap(Paint.Cap.BUTT);
            canvas.translate(x, y);

            Path p = new Path();
            p.moveTo(-radius, height);
            p.lineTo(-radius, 0);
            p.arcTo(-radius, -radius, radius, radius, -180, 270, false);
            p.lineTo(-radius, radius);

            float w = Math.max(canvas.getWidth(), canvas.getHeight())  * 1.414f;
            paint.setStyle(Paint.Style.FILL);

            int i=0;
            while (w > radius*2 + inner_w*2) {
                paint.setColor(0xFF000000 | palette[i % palette.length]);
                // for a slower but more complete version:
                // paint.setStrokeWidth(w);
                // canvas.drawPath(p, paint);
                canvas.drawOval(-w/2, -w/2, w/2, w/2, paint);
                w -= inner_w * (1.1f + Math.sin((i/20f + offset) * 3.14159f));
                i++;
            }

            // the innermost circle needs to be a constant color to avoid rapid flashing
            paint.setColor(0xFF000000 | palette[(darkest+1) % palette.length]);
            canvas.drawOval(-radius, -radius, radius, radius, paint);

            p.reset();
            p.moveTo(-radius, height);
            p.lineTo(-radius, 0);
            p.arcTo(-radius, -radius, radius, radius, -180, 270, false);
            p.lineTo(-radius + inner_w, radius);

            paint.setStyle(Paint.Style.STROKE);
            paint.setStrokeWidth(inner_w*2);
            paint.setColor(palette[darkest]);
            canvas.drawPath(p, paint);
            paint.setStrokeWidth(inner_w);
            paint.setColor(0xFFFFFFFF);
            canvas.drawPath(p, paint);
        }

        @Override
        public void setAlpha(int alpha) {

        }

        @Override
        public void setColorFilter(ColorFilter colorFilter) {
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
        public int getOpacity() {
            return 0;
    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;

        layout = new FrameLayout(this);
        setContentView(layout);
        getWindow().getDecorView().setSystemUiVisibility(
                View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
        getWindow().setNavigationBarColor(0);
        getWindow().setStatusBarColor(0);

        bg = new PBackground();
        layout.setBackground(bg);
        getActionBar().hide();

        final ContentResolver cr = getContentResolver();
        setContentView(R.layout.platlogo_layout);

        layout.setOnTouchListener(new View.OnTouchListener() {
            final String TOUCH_STATS = "touch.stats";
        mBackslash = new BackslashDrawable((int) (50 * dp));

            final PointerCoords pc0 = new PointerCoords();
            final PointerCoords pc1 = new PointerCoords();
        mOneView = findViewById(R.id.one);
        mOneView.setImageDrawable(new OneDrawable());
        mZeroView = findViewById(R.id.zero);
        mZeroView.setImageDrawable(new ZeroDrawable());

            double pressure_min, pressure_max;
            int maxPointers;
            int tapCount;
        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) {
                final float pressure = event.getPressure();
                measureTouchPressure(event);
                switch (event.getActionMasked()) {
                    case MotionEvent.ACTION_DOWN:
                        pressure_min = pressure_max = pressure;
                        // fall through
                    case MotionEvent.ACTION_MOVE:
                        if (pressure < pressure_min) pressure_min = pressure;
                        if (pressure > pressure_max) pressure_max = pressure;
                        final int pc = event.getPointerCount();
                        if (pc > maxPointers) maxPointers = pc;
                        if (pc > 1) {
                            event.getPointerCoords(0, pc0);
                            event.getPointerCoords(1, pc1);
                            bg.setRadius((float) Math.hypot(pc0.x - pc1.x, pc0.y - pc1.y) / 2f);
                        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_CANCEL:
                    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:
                        try {
                            final String touchDataJson = Settings.System.getString(cr, TOUCH_STATS);
                            final JSONObject touchData = new JSONObject(
                                    touchDataJson != null ? touchDataJson : "{}");
                            if (touchData.has("min")) {
                                pressure_min = Math.min(pressure_min, touchData.getDouble("min"));
                        v.performClick();
                        // fall through
                    case MotionEvent.ACTION_CANCEL:
                        v.animate().scaleX(1f).scaleY(1f);
                        if (mRotAnim != null) mRotAnim.cancel();
                        testOverlap();
                        break;
                }
                            if (touchData.has("max")) {
                                pressure_max = Math.max(pressure_max, touchData.getDouble("max"));
                return true;
            }
                            touchData.put("min", pressure_min);
                            touchData.put("max", pressure_max);
                            Settings.System.putString(cr, TOUCH_STATS, touchData.toString());
                        } catch (Exception e) {
                            Log.e("PlatLogoActivity", "Can't write touch settings", e);
        };

        findViewById(R.id.one).setOnTouchListener(tl);
        findViewById(R.id.zero).setOnTouchListener(tl);
        findViewById(R.id.text).setOnTouchListener(tl);
    }

                        if (maxPointers == 1) {
                            tapCount ++;
                            if (tapCount < 7) {
                                bg.randomizePalette();
                            } else {
    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 {
                            tapCount = 0;
                        }
                        maxPointers = 0;
                        break;
                }
                return true;
            mBackslash.stopAnimating();
        }
        });
    }

    private void launchNextStage() {
        final ContentResolver cr = getContentResolver();

        if (Settings.System.getLong(cr, Settings.System.EGG_MODE, 0) == 0) {
        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,
                        Settings.System.EGG_MODE,
                        "egg_mode", // Settings.System.EGG_MODE,
                        System.currentTimeMillis());
            } catch (RuntimeException e) {
                Log.e("PlatLogoActivity", "Can't write settings", e);
                Log.e("com.android.internal.app.PlatLogoActivity", "Can't write settings", e);
            }
        }
        try {
@@ -270,36 +185,206 @@ public class PlatLogoActivity extends Activity {
                        | Intent.FLAG_ACTIVITY_CLEAR_TASK)
                    .addCategory("com.android.internal.category.PLATLOGO"));
        } catch (ActivityNotFoundException ex) {
            Log.e("PlatLogoActivity", "No more eggs.");
            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();
    }

        bg.randomizePalette();
    @Override
    public void onStop() {
        syncTouchPressure();
        super.onStop();
    }

    static class ZeroDrawable extends Drawable {
        int mTintColor;

        anim = new TimeAnimator();
        anim.setTimeListener(
                new TimeAnimator.TimeListener() {
        @Override
                    public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
                        bg.setOffset((float) totalTime / 60000f);
                        bg.invalidateSelf();
        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();
        }
                });

        anim.start();
        @Override
        public void setAlpha(int alpha) { }

        @Override
        public void setColorFilter(ColorFilter colorFilter) { }

        @Override
        public void setTintList(ColorStateList tint) {
            mTintColor = tint.getDefaultColor();
        }

        @Override
    public void onStop() {
        if (anim != null) {
            anim.cancel();
            anim = null;
        public int getOpacity() {
            return PixelFormat.TRANSLUCENT;
        }
        super.onStop();
    }

    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();
            }
        }
    }
}
+2 −1
Original line number Diff line number Diff line
@@ -4656,8 +4656,9 @@
                android:process=":ui">
        </activity>
        <activity android:name="com.android.internal.app.PlatLogoActivity"
                android:theme="@style/Theme.Wallpaper.NoTitleBar.Fullscreen"
                android:theme="@style/Theme.DeviceDefault.DayNight"
                android:configChanges="orientation|keyboardHidden"
                android:icon="@drawable/platlogo"
                android:process=":ui">
        </activity>
        <activity android:name="com.android.internal.app.DisableCarModeActivity"
+42 −0
Original line number Diff line number Diff line
<!--
Copyright (C) 2015 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
        android:width="290dp"
        android:height="64dp"
        android:viewportWidth="290.0"
        android:viewportHeight="64.0">
    <path
        android:fillColor="#FF000000"
        android:pathData="M21.81,28.91c-7.44,0,-12.45,5.85,-12.45,13.37c0,7.52,5.01,13.37,12.45,13.37s12.45,-5.85,12.45,-13.37   C34.26,34.76,29.24,28.91,21.81,28.91 M20.13,20.55c6.02,0,11.03,3.09,13.37,6.43v-5.6l9.19,0l0,41.78l-6.24,0   c-1.63,0,-2.95,-1.32,-2.95,-2.95v-2.65C31.17,60.91,26.15,64,20.14,64C8.69,64,0,54.23,0,42.28C0,30.33,8.69,20.55,20.13,20.55"/>
    <path
        android:fillColor="#FF000000"
        android:pathData="M53.13,21.39l9.19,0l0,5.68c2.5,-4.18,7.27,-6.52,12.7,-6.52c9.69,0,15.96,6.85,15.96,17.46l0,25.15l-6.24,0   c-1.63,0,-2.95,-1.32,-2.95,-2.95l0,-20.7c0,-6.6,-3.34,-10.61,-8.69,-10.61c-6.1,0,-10.78,4.76,-10.78,13.7l0,20.55l-6.25,0   c-1.63,0,-2.95,-1.32,-2.95,-2.95L53.13,21.39z"/>
    <path
        android:fillColor="#FF000000"
        android:pathData="M120.06,28.91c-7.43,0,-12.45,5.85,-12.45,13.37c0,7.52,5.01,13.37,12.45,13.37c7.43,0,12.45,-5.85,12.45,-13.37   C132.51,34.76,127.5,28.91,120.06,28.91 M118.39,20.55c6.02,0,11.03,3.09,13.37,6.43l0,-26.49l9.19,0l0,62.66h-6.24   c-1.63,0,-2.95,-1.32,-2.95,-2.95v-2.65c-2.34,3.34,-7.35,6.43,-13.37,6.43c-11.45,0,-20.14,-9.77,-20.14,-21.72   C98.25,30.33,106.94,20.55,118.39,20.55"/>
    <path
        android:fillColor="#FF000000"
        android:pathData="M151.39,21.39l9.19,0v7.44c1.59,-4.76,6.27,-7.86,11.03,-7.86c1.17,0,2.34,0.08,3.59,0.34v9.44c-1.59,-0.5,-2.92,-0.75,-4.59,-0.75   c-5.26,0,-10.03,4.43,-10.03,12.78l0,20.39l-6.24,0c-1.63,0,-2.95,-1.32,-2.95,-2.95L151.39,21.39z"/>
    <path
        android:fillColor="#FF000000"
        android:pathData="M199.98,55.48c7.35,0,12.53,-5.77,12.53,-13.2c0,-7.44,-5.18,-13.2,-12.53,-13.2c-7.44,0,-12.62,5.77,-12.62,13.2   C187.37,49.71,192.55,55.48,199.98,55.48 M199.98,64c-12.37,0,-21.89,-9.61,-21.89,-21.72c0,-12.12,9.52,-21.73,21.89,-21.73   c12.37,0,21.89,9.61,21.89,21.73C221.87,54.39,212.35,64,199.98,64"/>
    <path
        android:fillColor="#FF000000"
        android:pathData="M229.32,21.39l9.19,0l0,41.78l-6.24,0c-1.63,0,-2.95,-1.32,-2.95,-2.95L229.32,21.39z M233.92,12.28   c-3.34,0,-6.18,-2.76,-6.18,-6.18c0,-3.34,2.84,-6.1,6.18,-6.1c3.43,0,6.1,2.76,6.1,6.1C240.02,9.53,237.34,12.28,233.92,12.28"/>
    <path
        android:fillColor="#FF000000"
        android:pathData="M267.87,28.91c-7.43,0,-12.45,5.85,-12.45,13.37c0,7.52,5.01,13.37,12.45,13.37c7.44,0,12.45,-5.85,12.45,-13.37   C280.32,34.76,275.31,28.91,267.87,28.91 M266.2,20.55c6.02,0,11.03,3.09,13.37,6.43l0,-26.49l9.19,0l0,62.66l-6.24,0   c-1.63,0,-2.95,-1.32,-2.95,-2.95v-2.65c-2.34,3.34,-7.35,6.43,-13.37,6.43c-11.44,0,-20.14,-9.77,-20.14,-21.72S254.76,20.55,266.2,20.55"/>
</vector>
+10 −16

File changed.

Preview size limit exceeded, changes collapsed.

+50 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading