502 lines
16 KiB
Java
502 lines
16 KiB
Java
/*
|
|
* Copyright (C) 2020 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 static android.os.VibrationEffect.Composition.PRIMITIVE_SPIN;
|
|
|
|
import android.animation.ObjectAnimator;
|
|
import android.animation.TimeAnimator;
|
|
import android.annotation.SuppressLint;
|
|
import android.app.ActionBar;
|
|
import android.app.Activity;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.ContentResolver;
|
|
import android.content.Intent;
|
|
import android.graphics.Canvas;
|
|
import android.graphics.Color;
|
|
import android.graphics.ColorFilter;
|
|
import android.graphics.Paint;
|
|
import android.graphics.PixelFormat;
|
|
import android.graphics.Rect;
|
|
import android.graphics.drawable.Drawable;
|
|
import android.os.Bundle;
|
|
import android.os.CombinedVibration;
|
|
import android.os.Handler;
|
|
import android.os.HandlerThread;
|
|
import android.os.Message;
|
|
import android.os.VibrationEffect;
|
|
import android.os.VibratorManager;
|
|
import android.provider.Settings;
|
|
import android.util.DisplayMetrics;
|
|
import android.util.Log;
|
|
import android.view.Gravity;
|
|
import android.view.HapticFeedbackConstants;
|
|
import android.view.KeyEvent;
|
|
import android.view.MotionEvent;
|
|
import android.view.View;
|
|
import android.view.WindowInsets;
|
|
import android.widget.FrameLayout;
|
|
import android.widget.ImageView;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.annotation.Nullable;
|
|
|
|
import com.android.internal.R;
|
|
|
|
import org.json.JSONObject;
|
|
|
|
import java.util.Random;
|
|
|
|
/**
|
|
* @hide
|
|
*/
|
|
public class PlatLogoActivity extends Activity {
|
|
private static final String TAG = "PlatLogoActivity";
|
|
|
|
private static final long LAUNCH_TIME = 5000L;
|
|
|
|
private static final String U_EGG_UNLOCK_SETTING = "egg_mode_u";
|
|
|
|
private static final float MIN_WARP = 1f;
|
|
private static final float MAX_WARP = 10f; // after all these years
|
|
private static final boolean FINISH_AFTER_NEXT_STAGE_LAUNCH = false;
|
|
|
|
private ImageView mLogo;
|
|
private Starfield mStarfield;
|
|
|
|
private FrameLayout mLayout;
|
|
|
|
private TimeAnimator mAnim;
|
|
private ObjectAnimator mWarpAnim;
|
|
private Random mRandom;
|
|
private float mDp;
|
|
|
|
private RumblePack mRumble;
|
|
|
|
private boolean mAnimationsEnabled = true;
|
|
|
|
private final View.OnTouchListener mTouchListener = new View.OnTouchListener() {
|
|
@Override
|
|
public boolean onTouch(View v, MotionEvent event) {
|
|
switch (event.getActionMasked()) {
|
|
case MotionEvent.ACTION_DOWN:
|
|
measureTouchPressure(event);
|
|
startWarp();
|
|
break;
|
|
case MotionEvent.ACTION_UP:
|
|
case MotionEvent.ACTION_CANCEL:
|
|
stopWarp();
|
|
break;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
};
|
|
|
|
private final Runnable mLaunchNextStage = () -> {
|
|
stopWarp();
|
|
launchNextStage(false);
|
|
};
|
|
|
|
private final TimeAnimator.TimeListener mTimeListener = new TimeAnimator.TimeListener() {
|
|
@Override
|
|
public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
|
|
mStarfield.update(deltaTime);
|
|
final float warpFrac = (mStarfield.getWarp() - MIN_WARP) / (MAX_WARP - MIN_WARP);
|
|
if (mAnimationsEnabled) {
|
|
mLogo.setTranslationX(mRandom.nextFloat() * warpFrac * 5 * mDp);
|
|
mLogo.setTranslationY(mRandom.nextFloat() * warpFrac * 5 * mDp);
|
|
}
|
|
if (warpFrac > 0f) {
|
|
mRumble.rumble(warpFrac);
|
|
}
|
|
mLayout.postInvalidate();
|
|
}
|
|
};
|
|
|
|
private class RumblePack implements Handler.Callback {
|
|
private static final int MSG = 6464;
|
|
private static final int INTERVAL = 50;
|
|
|
|
private final VibratorManager mVibeMan;
|
|
private final HandlerThread mVibeThread;
|
|
private final Handler mVibeHandler;
|
|
private boolean mSpinPrimitiveSupported;
|
|
|
|
private long mLastVibe = 0;
|
|
|
|
@SuppressLint("MissingPermission")
|
|
@Override
|
|
public boolean handleMessage(Message msg) {
|
|
final float warpFrac = msg.arg1 / 100f;
|
|
if (mSpinPrimitiveSupported) {
|
|
if (msg.getWhen() > mLastVibe + INTERVAL) {
|
|
mLastVibe = msg.getWhen();
|
|
mVibeMan.vibrate(CombinedVibration.createParallel(
|
|
VibrationEffect.startComposition()
|
|
.addPrimitive(PRIMITIVE_SPIN, (float) Math.pow(warpFrac, 3.0))
|
|
.compose()
|
|
));
|
|
}
|
|
} else {
|
|
if (mRandom.nextFloat() < warpFrac) {
|
|
mLogo.performHapticFeedback(HapticFeedbackConstants.CLOCK_TICK);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
RumblePack() {
|
|
mVibeMan = getSystemService(VibratorManager.class);
|
|
mSpinPrimitiveSupported = mVibeMan.getDefaultVibrator()
|
|
.areAllPrimitivesSupported(PRIMITIVE_SPIN);
|
|
|
|
mVibeThread = new HandlerThread("VibratorThread");
|
|
mVibeThread.start();
|
|
mVibeHandler = Handler.createAsync(mVibeThread.getLooper(), this);
|
|
}
|
|
|
|
public void destroy() {
|
|
mVibeThread.quit();
|
|
}
|
|
|
|
private void rumble(float warpFrac) {
|
|
if (!mVibeThread.isAlive()) return;
|
|
|
|
final Message msg = Message.obtain();
|
|
msg.what = MSG;
|
|
msg.arg1 = (int) (warpFrac * 100);
|
|
mVibeHandler.removeMessages(MSG);
|
|
mVibeHandler.sendMessage(msg);
|
|
}
|
|
|
|
}
|
|
|
|
@Override
|
|
protected void onDestroy() {
|
|
mRumble.destroy();
|
|
|
|
super.onDestroy();
|
|
}
|
|
|
|
@Override
|
|
protected void onCreate(Bundle savedInstanceState) {
|
|
super.onCreate(savedInstanceState);
|
|
|
|
getWindow().setDecorFitsSystemWindows(false);
|
|
getWindow().setNavigationBarColor(0);
|
|
getWindow().setStatusBarColor(0);
|
|
getWindow().getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
|
|
|
|
final ActionBar ab = getActionBar();
|
|
if (ab != null) ab.hide();
|
|
|
|
try {
|
|
mAnimationsEnabled = Settings.Global.getFloat(getContentResolver(),
|
|
Settings.Global.ANIMATOR_DURATION_SCALE) > 0f;
|
|
} catch (Settings.SettingNotFoundException e) {
|
|
mAnimationsEnabled = true;
|
|
}
|
|
|
|
mRumble = new RumblePack();
|
|
|
|
mLayout = new FrameLayout(this);
|
|
mRandom = new Random();
|
|
mDp = getResources().getDisplayMetrics().density;
|
|
mStarfield = new Starfield(mRandom, mDp * 2f);
|
|
mStarfield.setVelocity(
|
|
200f * (mRandom.nextFloat() - 0.5f),
|
|
200f * (mRandom.nextFloat() - 0.5f));
|
|
mLayout.setBackground(mStarfield);
|
|
|
|
final DisplayMetrics dm = getResources().getDisplayMetrics();
|
|
final float dp = dm.density;
|
|
final int minSide = Math.min(dm.widthPixels, dm.heightPixels);
|
|
final int widgetSize = (int) (minSide * 0.75);
|
|
final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(widgetSize, widgetSize);
|
|
lp.gravity = Gravity.CENTER;
|
|
|
|
mLogo = new ImageView(this);
|
|
mLogo.setImageResource(R.drawable.platlogo);
|
|
mLogo.setOnTouchListener(mTouchListener);
|
|
mLogo.requestFocus();
|
|
mLayout.addView(mLogo, lp);
|
|
|
|
Log.v(TAG, "Hello");
|
|
|
|
setContentView(mLayout);
|
|
}
|
|
|
|
private void startAnimating() {
|
|
mAnim = new TimeAnimator();
|
|
mAnim.setTimeListener(mTimeListener);
|
|
mAnim.start();
|
|
}
|
|
|
|
private void stopAnimating() {
|
|
mAnim.cancel();
|
|
mAnim = null;
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
|
if (keyCode == KeyEvent.KEYCODE_SPACE) {
|
|
if (event.getRepeatCount() == 0) {
|
|
startWarp();
|
|
}
|
|
return true;
|
|
}
|
|
return super.onKeyDown(keyCode,event);
|
|
}
|
|
|
|
@Override
|
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
|
if (keyCode == KeyEvent.KEYCODE_SPACE) {
|
|
stopWarp();
|
|
return true;
|
|
}
|
|
return super.onKeyUp(keyCode,event);
|
|
}
|
|
|
|
private void startWarp() {
|
|
stopWarp();
|
|
mWarpAnim = ObjectAnimator.ofFloat(mStarfield, "warp", MIN_WARP, MAX_WARP)
|
|
.setDuration(LAUNCH_TIME);
|
|
mWarpAnim.start();
|
|
|
|
mLogo.postDelayed(mLaunchNextStage, LAUNCH_TIME + 1000L);
|
|
}
|
|
|
|
private void stopWarp() {
|
|
if (mWarpAnim != null) {
|
|
mWarpAnim.cancel();
|
|
mWarpAnim.removeAllListeners();
|
|
mWarpAnim = null;
|
|
}
|
|
mStarfield.setWarp(1f);
|
|
mLogo.removeCallbacks(mLaunchNextStage);
|
|
}
|
|
|
|
@Override
|
|
public void onResume() {
|
|
super.onResume();
|
|
startAnimating();
|
|
}
|
|
|
|
@Override
|
|
public void onPause() {
|
|
stopWarp();
|
|
stopAnimating();
|
|
super.onPause();
|
|
}
|
|
|
|
private boolean shouldWriteSettings() {
|
|
return getPackageName().equals("android");
|
|
}
|
|
|
|
private void launchNextStage(boolean locked) {
|
|
final ContentResolver cr = getContentResolver();
|
|
|
|
try {
|
|
if (shouldWriteSettings()) {
|
|
Log.v(TAG, "Saving egg locked=" + locked);
|
|
syncTouchPressure();
|
|
Settings.System.putLong(cr,
|
|
U_EGG_UNLOCK_SETTING,
|
|
locked ? 0 : System.currentTimeMillis());
|
|
}
|
|
} catch (RuntimeException e) {
|
|
Log.e(TAG, "Can't write settings", e);
|
|
}
|
|
|
|
try {
|
|
final Intent eggActivity = new Intent(Intent.ACTION_MAIN)
|
|
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
|
|
| Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
|
.addCategory("com.android.internal.category.PLATLOGO");
|
|
Log.v(TAG, "launching: " + eggActivity);
|
|
startActivity(eggActivity);
|
|
} catch (ActivityNotFoundException ex) {
|
|
Log.e("com.android.internal.app.PlatLogoActivity", "No more eggs.");
|
|
}
|
|
if (FINISH_AFTER_NEXT_STAGE_LAUNCH) {
|
|
finish(); // we're done here.
|
|
}
|
|
}
|
|
|
|
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);
|
|
if (shouldWriteSettings()) {
|
|
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();
|
|
}
|
|
|
|
private static class Starfield extends Drawable {
|
|
private static final int NUM_STARS = 34; // Build.VERSION_CODES.UPSIDE_DOWN_CAKE
|
|
|
|
private static final int NUM_PLANES = 2;
|
|
private final float[] mStars = new float[NUM_STARS * 4];
|
|
private float mVx, mVy;
|
|
private long mDt = 0;
|
|
private final Paint mStarPaint;
|
|
|
|
private final Random mRng;
|
|
private final float mSize;
|
|
|
|
private final Rect mSpace = new Rect();
|
|
private float mWarp = 1f;
|
|
|
|
private float mBuffer;
|
|
|
|
public void setWarp(float warp) {
|
|
mWarp = warp;
|
|
}
|
|
|
|
public float getWarp() {
|
|
return mWarp;
|
|
}
|
|
|
|
Starfield(Random rng, float size) {
|
|
mRng = rng;
|
|
mSize = size;
|
|
mStarPaint = new Paint();
|
|
mStarPaint.setStyle(Paint.Style.STROKE);
|
|
mStarPaint.setColor(Color.WHITE);
|
|
}
|
|
|
|
@Override
|
|
public void onBoundsChange(Rect bounds) {
|
|
mSpace.set(bounds);
|
|
mBuffer = mSize * NUM_PLANES * 2 * MAX_WARP;
|
|
mSpace.inset(-(int) mBuffer, -(int) mBuffer);
|
|
final float w = mSpace.width();
|
|
final float h = mSpace.height();
|
|
for (int i = 0; i < NUM_STARS; i++) {
|
|
mStars[4 * i] = mRng.nextFloat() * w;
|
|
mStars[4 * i + 1] = mRng.nextFloat() * h;
|
|
mStars[4 * i + 2] = mStars[4 * i];
|
|
mStars[4 * i + 3] = mStars[4 * i + 1];
|
|
}
|
|
}
|
|
|
|
public void setVelocity(float x, float y) {
|
|
mVx = x;
|
|
mVy = y;
|
|
}
|
|
|
|
@Override
|
|
public void draw(@NonNull Canvas canvas) {
|
|
final float dtSec = mDt / 1000f;
|
|
final float dx = (mVx * dtSec * mWarp);
|
|
final float dy = (mVy * dtSec * mWarp);
|
|
|
|
final boolean inWarp = mWarp > 1f;
|
|
|
|
canvas.drawColor(Color.BLACK); // 0xFF16161D);
|
|
|
|
if (mDt > 0 && mDt < 1000) {
|
|
canvas.translate(
|
|
-(mBuffer) + mRng.nextFloat() * (mWarp - 1f),
|
|
-(mBuffer) + mRng.nextFloat() * (mWarp - 1f)
|
|
);
|
|
final float w = mSpace.width();
|
|
final float h = mSpace.height();
|
|
for (int i = 0; i < NUM_STARS; i++) {
|
|
final int plane = (int) ((((float) i) / NUM_STARS) * NUM_PLANES) + 1;
|
|
mStars[4 * i + 2] = (mStars[4 * i + 2] + dx * plane + w) % w;
|
|
mStars[4 * i + 3] = (mStars[4 * i + 3] + dy * plane + h) % h;
|
|
mStars[4 * i + 0] = inWarp ? mStars[4 * i + 2] - dx * mWarp * 2 * plane : -100;
|
|
mStars[4 * i + 1] = inWarp ? mStars[4 * i + 3] - dy * mWarp * 2 * plane : -100;
|
|
}
|
|
}
|
|
final int slice = (mStars.length / NUM_PLANES / 4) * 4;
|
|
for (int p = 0; p < NUM_PLANES; p++) {
|
|
mStarPaint.setStrokeWidth(mSize * (p + 1));
|
|
if (inWarp) {
|
|
canvas.drawLines(mStars, p * slice, slice, mStarPaint);
|
|
}
|
|
canvas.drawPoints(mStars, p * slice, slice, mStarPaint);
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void setAlpha(int alpha) {
|
|
|
|
}
|
|
|
|
@Override
|
|
public void setColorFilter(@Nullable ColorFilter colorFilter) {
|
|
|
|
}
|
|
|
|
@Override
|
|
public int getOpacity() {
|
|
return PixelFormat.OPAQUE;
|
|
}
|
|
|
|
public void update(long dt) {
|
|
mDt = dt;
|
|
}
|
|
}
|
|
} |