diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index ffb7a19b..ddd6796f 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -49,6 +49,7 @@
+
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 626fda35..7cc8db3d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@
### v3.2.1
+- Add Rocket Launcher Easter Egg
- Optimize the setting of theme logic
- Optimize APK file size
- Project modularization
diff --git a/CHANGELOG_zh.md b/CHANGELOG_zh.md
index e2f00859..e9bc82bc 100644
--- a/CHANGELOG_zh.md
+++ b/CHANGELOG_zh.md
@@ -2,6 +2,7 @@
### v3.2.1
+- 添加 Rocket Launcher 彩蛋
- 优化设置主题逻辑
- 优化 APK 文件大小
- 项目模块化
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 2d73b936..d5193254 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -150,6 +150,7 @@ dependencies {
implementation(project(":feature:crash"))
implementation(project(":feature:embedding-splits"))
+ implementation(project(":eggs:RocketLauncher"))
implementation(project(":eggs:AndroidNext"))
implementation(project(":eggs:VanillaIceCream"))
implementation(project(":eggs:UpsideDownCake"))
diff --git a/app/src/main/java/com/dede/android_eggs/views/settings/compose/prefs/ComponentManagerPref.kt b/app/src/main/java/com/dede/android_eggs/views/settings/compose/prefs/ComponentManagerPref.kt
index e6529a72..7a735252 100644
--- a/app/src/main/java/com/dede/android_eggs/views/settings/compose/prefs/ComponentManagerPref.kt
+++ b/app/src/main/java/com/dede/android_eggs/views/settings/compose/prefs/ComponentManagerPref.kt
@@ -1,5 +1,8 @@
package com.dede.android_eggs.views.settings.compose.prefs
+import android.content.Intent
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.AppRegistration
@@ -11,11 +14,14 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewmodel.compose.viewModel
-import com.dede.android_eggs.R
+import com.android.launcher2.RocketLauncher
+import com.dede.android_eggs.util.LocalEvent
import com.dede.android_eggs.views.main.compose.EasterEggLogo
import com.dede.android_eggs.views.main.util.EasterEggHelp.VersionFormatter
import com.dede.android_eggs.views.settings.compose.basic.ExpandOptionsPref
+import com.dede.android_eggs.views.settings.compose.basic.Option
import com.dede.android_eggs.views.settings.compose.basic.OptionShapes
+import com.dede.android_eggs.views.settings.compose.basic.SettingPrefUtil
import com.dede.android_eggs.views.settings.compose.basic.SwitchOption
import com.dede.basic.provider.ComponentProvider
import dagger.hilt.android.lifecycle.HiltViewModel
@@ -33,8 +39,27 @@ fun ComponentManagerPref(viewModel: ComponentManagerViewModel = viewModel()) {
leadingIcon = Icons.Rounded.AppRegistration,
title = stringResource(id = StringsR.string.label_component_manager),
) {
- val componentCount = componentList.size
val context = LocalContext.current
+ Option(
+ shape = OptionShapes.borderShape,
+ leadingIcon = {
+ EasterEggLogo(
+ res = com.android.launcher2.R.mipmap.ic_rocket_launcher,
+ modifier = Modifier.size(30.dp),
+ contentDescription = stringResource(id = com.android.launcher2.R.string.dream_name),
+ )
+ },
+ title = stringResource(com.android.launcher2.R.string.dream_name),
+ desc = stringResource(com.android.launcher2.R.string.rocket_launcher_desc),
+ onClick = {
+ context.startActivity(Intent(context, RocketLauncher::class.java))
+ LocalEvent.poster().post(SettingPrefUtil.ACTION_CLOSE_SETTING)
+ }
+ )
+
+ Spacer(modifier = Modifier.height(1.dp))
+
+ val componentCount = componentList.size
componentList.forEachIndexed { index, component ->
val formatter = VersionFormatter.create(component.apiLevelRange, component.nicknameRes)
SwitchOption(
@@ -42,7 +67,7 @@ fun ComponentManagerPref(viewModel: ComponentManagerViewModel = viewModel()) {
leadingIcon = {
EasterEggLogo(
res = component.iconRes,
- modifier = Modifier.size(28.dp),
+ modifier = Modifier.size(30.dp),
contentDescription = stringResource(id = component.nameRes),
)
},
diff --git a/basic/src/main/java/com/dede/android_eggs/util/LocalEvent.kt b/basic/src/main/java/com/dede/android_eggs/util/LocalEvent.kt
index 7de57125..e48f7503 100644
--- a/basic/src/main/java/com/dede/android_eggs/util/LocalEvent.kt
+++ b/basic/src/main/java/com/dede/android_eggs/util/LocalEvent.kt
@@ -50,7 +50,7 @@ object LocalEvent {
fun trimToSize(size: Int) {
var trimCount = 0
- val keys = localEventLiveDataMap.keys
+ val keys = HashSet(localEventLiveDataMap.keys)
for (key in keys) {
val liveData = localEventLiveDataMap[key]
if (liveData == null) {
diff --git a/eggs/AndroidNext/src/main/java/com/android_next/egg/AndroidNextEasterEgg.kt b/eggs/AndroidNext/src/main/java/com/android_next/egg/AndroidNextEasterEgg.kt
index fb6cb08f..4a28cd55 100644
--- a/eggs/AndroidNext/src/main/java/com/android_next/egg/AndroidNextEasterEgg.kt
+++ b/eggs/AndroidNext/src/main/java/com/android_next/egg/AndroidNextEasterEgg.kt
@@ -64,7 +64,7 @@ object AndroidNextEasterEgg : EasterEggProvider {
return ImageView(context).apply {
setImageDrawable(context.requireDrawable(PLATLOGO_RES))
setPadding(12.dp)
- setBackgroundColor(0xFF_202124.toInt())
+ setBackgroundColor(0xFF_1B1E22.toInt())
}
}
diff --git a/eggs/RocketLauncher/.gitignore b/eggs/RocketLauncher/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/eggs/RocketLauncher/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/eggs/RocketLauncher/build.gradle.kts b/eggs/RocketLauncher/build.gradle.kts
new file mode 100644
index 00000000..07724cfa
--- /dev/null
+++ b/eggs/RocketLauncher/build.gradle.kts
@@ -0,0 +1,7 @@
+plugins {
+ id("easter.egg.library")
+}
+
+android {
+ namespace = "com.android.launcher2"
+}
diff --git a/eggs/RocketLauncher/lint-baseline.xml b/eggs/RocketLauncher/lint-baseline.xml
new file mode 100644
index 00000000..1cc6eca7
--- /dev/null
+++ b/eggs/RocketLauncher/lint-baseline.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eggs/RocketLauncher/src/main/AndroidManifest.xml b/eggs/RocketLauncher/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..af4114d8
--- /dev/null
+++ b/eggs/RocketLauncher/src/main/AndroidManifest.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/eggs/RocketLauncher/src/main/java/android/support/v13/dreams/BasicDream.java b/eggs/RocketLauncher/src/main/java/android/support/v13/dreams/BasicDream.java
new file mode 100644
index 00000000..d436c703
--- /dev/null
+++ b/eggs/RocketLauncher/src/main/java/android/support/v13/dreams/BasicDream.java
@@ -0,0 +1,132 @@
+package android.support.v13.dreams;
+
+import android.app.Activity;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Canvas;
+import android.os.BatteryManager;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+
+import androidx.annotation.NonNull;
+
+public class BasicDream extends Activity {
+ /** A simple Dream implementation that can be subclassed to write your own Dreams. Any Activity
+ * may be used as a Dream, so this class isn't strictly necessary. However, it does take care of
+ * a number of housekeeping tasks that most screensavers will want:
+ *
+ * - Keep the screen on as long as the device is plugged in
+ *
- Exit (using
finish()
) as soon as any user activity is detected
+ * - Hide the system UI (courtesy its inner {@link BasicDreamView} class)
+ *
+ * Finally, it exposes an {@link BasicDream#onDraw(Canvas)} method that you can override to do
+ * your own drawing. As with a View,
call {@link BasicDream#invalidate()} any time
+ * to request that a new frame be drawn.
+ */
+ private final static String TAG = "BasicDream";
+ private final static boolean DEBUG = true;
+
+ private View mView;
+ private boolean mPlugged = false;
+
+ public class BasicDreamView extends View {
+ /** A simple view that just calls back to {@link BasicDream#onDraw(Canvas) onDraw} on its
+ * parent BasicDream Activity. It also hides the system UI if this feature is available on
+ * the current device.
+ */
+ public BasicDreamView(Context c) {
+ super(c);
+ }
+
+ public BasicDreamView(Context c, AttributeSet at) {
+ super(c, at);
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
+ }
+
+ @Override
+ public void onDraw(@NonNull Canvas c) {
+ BasicDream.this.onDraw(c);
+ }
+ }
+
+ private final BroadcastReceiver mPowerIntentReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ final String action = intent.getAction();
+ if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+ // Only keep the screen on if we're plugged in.
+ boolean plugged = (1 == intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0));
+
+ if (plugged != mPlugged) {
+ if (DEBUG) Log.d(TAG, "now " + (plugged ? "plugged in" : "unplugged"));
+
+ mPlugged = plugged;
+ if (mPlugged) {
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ } else {
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+ }
+ }
+ }
+ };
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ setContentView(new BasicDreamView(this));
+ getWindow().addFlags(
+ WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ );
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_BATTERY_CHANGED);
+ registerReceiver(mPowerIntentReceiver, filter);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ // Anything that would pause this activity should probably end the screensaver.
+ if (DEBUG) Log.d(TAG, "exiting onPause");
+ finish();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ unregisterReceiver(mPowerIntentReceiver);
+ }
+
+ protected View getContentView() {
+ return mView;
+ }
+
+ @Override
+ public void setContentView(View v) {
+ super.setContentView(v);
+ mView = v;
+ }
+
+ protected void invalidate() {
+ getContentView().invalidate();
+ }
+
+ public void onDraw(Canvas c) {
+ }
+
+ public void onUserInteraction() {
+ if (DEBUG) Log.d(TAG, "exiting onUserInteraction");
+ finish();
+ }
+}
diff --git a/eggs/RocketLauncher/src/main/java/com/android/launcher2/RocketLauncher.java b/eggs/RocketLauncher/src/main/java/com/android/launcher2/RocketLauncher.java
new file mode 100644
index 00000000..2af7ef17
--- /dev/null
+++ b/eggs/RocketLauncher/src/main/java/com/android/launcher2/RocketLauncher.java
@@ -0,0 +1,445 @@
+/*);
+ * Copyright (C) 2011 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.
+ */
+
+// TODO:
+// background stellar matter:
+// - add some slow horizontal parallax motion, or perhaps veeeeery gradual outward drift
+
+package com.android.launcher2;
+
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeAnimator;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Handler;
+import android.support.v13.dreams.BasicDream;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+
+import com.dede.basic.provider.EasterEgg;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Random;
+
+import dagger.hilt.EntryPoint;
+import dagger.hilt.InstallIn;
+import dagger.hilt.android.EntryPointAccessors;
+import dagger.hilt.components.SingletonComponent;
+
+public class RocketLauncher extends BasicDream {
+ public static final boolean ROCKET_LAUNCHER = true;
+
+ public static class Board extends FrameLayout {
+ public static final boolean FIXED_STARS = true;
+ public static final boolean FLYING_STARS = true;
+ public static final int NUM_ICONS = 20;
+
+ public static final float MANEUVERING_THRUST_SCALE = 0.1f; // tenth speed
+ private boolean mManeuveringThrusters = false;
+ private float mSpeedScale = 1.0f;
+
+ public static final int LAUNCH_ZOOM_TIME = 400; // ms
+
+ HashMap mIcons;
+ ComponentName[] mComponentNames;
+
+ static Random sRNG = new Random();
+
+ static float lerp(float a, float b, float f) {
+ return (b - a) * f + a;
+ }
+
+ static float randfrange(float a, float b) {
+ return lerp(a, b, sRNG.nextFloat());
+ }
+
+ static int randsign() {
+ return sRNG.nextBoolean() ? 1 : -1;
+ }
+
+ static E pick(E[] array) {
+ if (array.length == 0) return null;
+ return array[sRNG.nextInt(array.length)];
+ }
+
+ public class FlyingIcon extends ImageView {
+ public static final float VMAX = 1000.0f;
+ public static final float VMIN = 100.0f;
+ public static final float ANGULAR_VMAX = 45f;
+ public static final float ANGULAR_VMIN = 0f;
+ public static final float SCALE_MIN = 0.5f;
+ public static final float SCALE_MAX = 4f;
+
+ public float v, vr;
+
+ public final float[] hsv = new float[3];
+
+ public float angle, anglex, angley;
+ public float fuse;
+ public float dist;
+ public float endscale;
+ public float boardCenterX, boardCenterY;
+
+ public ComponentName component;
+
+ public FlyingIcon(Context context, AttributeSet as) {
+ super(context, as);
+ setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
+ setBackgroundResource(R.drawable.flying_icon_bg);
+ //android.util.Log.d("RocketLauncher", "ctor: " + this);
+ hsv[1] = 1f;
+ hsv[2] = 1f;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (!mManeuveringThrusters || component == null) {
+ return false;
+ }
+ if (getAlpha() < 0.5f) {
+ setPressed(false);
+ return false;
+ }
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ setPressed(true);
+ Board.this.resetWarpTimer();
+ break;
+ case MotionEvent.ACTION_MOVE:
+ final Rect hit = new Rect();
+ final Point offset = new Point();
+ getGlobalVisibleRect(hit, offset);
+ final int globx = (int) event.getX() + offset.x;
+ final int globy = (int) event.getY() + offset.y;
+ setPressed(hit.contains(globx, globy));
+ Board.this.resetWarpTimer();
+ break;
+ case MotionEvent.ACTION_UP:
+ if (isPressed()) {
+ setPressed(false);
+ postDelayed(new Runnable() {
+ public void run() {
+ try {
+ getContext().startActivity(new Intent(Intent.ACTION_MAIN)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ .setComponent(component));
+ } catch (android.content.ActivityNotFoundException e) {
+ e.printStackTrace();
+ } catch (SecurityException e) {
+ e.printStackTrace();
+ }
+ }
+ }, LAUNCH_ZOOM_TIME);
+ endscale = 0;
+ AnimatorSet s = new AnimatorSet();
+ s.playTogether(
+ ObjectAnimator.ofFloat(this, "scaleX", 15f),
+ ObjectAnimator.ofFloat(this, "scaleY", 15f),
+ ObjectAnimator.ofFloat(this, "alpha", 0f)
+ );
+
+ // make sure things are still moving until the very last instant the
+ // activity is visible
+ s.setDuration((int) (LAUNCH_ZOOM_TIME * 1.25));
+ s.setInterpolator(new android.view.animation.AccelerateInterpolator(3));
+ s.start();
+ }
+ break;
+ }
+ return true;
+ }
+
+ public String toString() {
+ return String.format("<'%s' @ (%.1f, %.1f) v=%.1f a=%.1f dist/fuse=%.1f/%.1f>",
+ "icon", getX(), getY(), v, angle, dist, fuse);
+ }
+
+ public void randomizeIcon() {
+ component = pick(mComponentNames);
+ setImageDrawable(mIcons.get(component));
+ }
+
+ public void randomize() {
+ v = randfrange(VMIN, VMAX);
+ angle = randfrange(0, 360f);
+ anglex = (float) Math.sin(angle / 180. * Math.PI);
+ angley = (float) Math.cos(angle / 180. * Math.PI);
+ vr = randfrange(ANGULAR_VMIN, ANGULAR_VMAX) * randsign();
+ endscale = randfrange(SCALE_MIN, SCALE_MAX);
+
+ randomizeIcon();
+ }
+
+ public void reset() {
+ randomize();
+ boardCenterX = (Board.this.getWidth() - getWidth()) / 2;
+ boardCenterY = (Board.this.getHeight() - getHeight()) / 2;
+ setX(boardCenterX);
+ setY(boardCenterY);
+ fuse = (float) Math.max(boardCenterX, boardCenterY);
+ setRotation(180 - angle);
+ setScaleX(0f);
+ setScaleY(0f);
+ dist = 0;
+ setAlpha(0f);
+ }
+
+ public void update(float dt) {
+ dist += v * dt;
+ setX(getX() + anglex * v * dt);
+ setY(getY() + angley * v * dt);
+ //setRotation(getRotation() + vr * dt);
+ if (endscale > 0) {
+ float scale = lerp(0, endscale, (float) Math.sqrt(dist / fuse));
+ setScaleX(scale * lerp(1f, 0.75f, (float) Math.pow((v - VMIN) / (VMAX - VMIN), 3)));
+ setScaleY(scale * lerp(1f, 1.5f, (float) Math.pow((v - VMIN) / (VMAX - VMIN), 3)));
+ final float q1 = fuse * 0.15f;
+ final float q4 = fuse * 0.75f;
+ if (dist < q1) {
+ setAlpha((float) Math.sqrt(dist / q1));
+ } else if (dist > q4) {
+ setAlpha((dist >= fuse) ? 0f : (1f - (float) Math.pow((dist - q4) / (fuse - q4), 2)));
+ } else {
+ setAlpha(1f);
+ }
+ }
+ }
+ }
+
+ public class FlyingStar extends FlyingIcon {
+ public FlyingStar(Context context, AttributeSet as) {
+ super(context, as);
+ }
+
+ public void randomizeIcon() {
+ setImageResource(R.drawable.widget_resize_handle_bottom);
+ }
+
+ public void randomize() {
+ super.randomize();
+ v = randfrange(VMAX * 0.75f, VMAX * 2f); // fasticate
+ endscale = randfrange(1f, 2f); // ensmallen
+ }
+ }
+
+ @EntryPoint
+ @InstallIn(SingletonComponent.class)
+ interface RocketLauncherEntryPoint {
+ List getEasterEggs();
+ }
+
+ TimeAnimator mAnim;
+
+ public Board(Context context, AttributeSet as) {
+ super(context, as);
+
+ // Inject in DreamService and Activity
+ List easterEggs = EntryPointAccessors
+ .fromApplication(getContext(), RocketLauncherEntryPoint.class)
+ .getEasterEggs();
+
+ setBackgroundColor(0xFF000000);
+
+ mIcons = Utils.convertComponentNameDrawableIcons(getContext(), easterEggs);
+// LauncherApplication app = (LauncherApplication)context.getApplicationContext();
+// mIcons = app.getIconCache().getAllIcons();
+ mComponentNames = new ComponentName[mIcons.size()];
+ mComponentNames = mIcons.keySet().toArray(mComponentNames);
+ }
+
+ private void reset() {
+ removeAllViews();
+
+ final ViewGroup.LayoutParams wrap = new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+
+ if (FIXED_STARS) {
+ for (int i = 0; i < 20; i++) {
+ ImageView fixedStar = new ImageView(getContext(), null);
+ fixedStar.setImageResource(R.drawable.widget_resize_handle_bottom);
+ final float s = randfrange(0.25f, 0.75f);
+ fixedStar.setScaleX(s);
+ fixedStar.setScaleY(s);
+ fixedStar.setAlpha(0.75f);
+ addView(fixedStar, wrap);
+ fixedStar.setX(randfrange(0, getWidth()));
+ fixedStar.setY(randfrange(0, getHeight()));
+ }
+ }
+
+ for (int i = 0; i < NUM_ICONS * 2; i++) {
+ FlyingIcon nv = (FLYING_STARS && (i < NUM_ICONS))
+ ? new FlyingStar(getContext(), null)
+ : new FlyingIcon(getContext(), null);
+ addView(nv, wrap);
+ nv.reset();
+ }
+
+ mAnim = new TimeAnimator();
+ mAnim.setTimeListener(new TimeAnimator.TimeListener() {
+ public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+ // setRotation(totalTime * 0.01f); // not as cool as you would think
+
+ final int START_ZOOM_TIME = 3000;
+ if (totalTime < START_ZOOM_TIME) {
+ final float x = totalTime / (float) START_ZOOM_TIME;
+ final float s = 1f - (float) Math.pow(x - 1, 4);
+ setScaleX(s);
+ setScaleY(s);
+ } else {
+ setScaleX(1.0f);
+ setScaleY(1.0f);
+ }
+
+ if (mManeuveringThrusters) {
+ if (mSpeedScale > MANEUVERING_THRUST_SCALE) {
+ mSpeedScale -= (2 * deltaTime / 1000f);
+ }
+ if (mSpeedScale < MANEUVERING_THRUST_SCALE) {
+ mSpeedScale = MANEUVERING_THRUST_SCALE;
+ }
+ } else {
+ if (mSpeedScale < 1.0f) {
+ mSpeedScale += (deltaTime / 1000f);
+ }
+ if (mSpeedScale > 1.0f) {
+ mSpeedScale = 1.0f;
+ }
+ }
+
+ for (int i = 0; i < getChildCount(); i++) {
+ View v = getChildAt(i);
+ if (!(v instanceof FlyingIcon)) continue;
+ FlyingIcon nv = (FlyingIcon) v;
+ nv.update(deltaTime / 1000f * mSpeedScale);
+ final float scaledWidth = nv.getWidth() * nv.getScaleX();
+ final float scaledHeight = nv.getHeight() * nv.getScaleY();
+ if (nv.getX() + scaledWidth < 0
+ || nv.getX() - scaledWidth > getWidth()
+ || nv.getY() + scaledHeight < 0
+ || nv.getY() - scaledHeight > getHeight()) {
+ nv.reset();
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ setLayerType(View.LAYER_TYPE_HARDWARE, null);
+ setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
+
+ // fix size
+// reset();
+// mAnim.start();
+ }
+
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ super.onSizeChanged(w, h, oldw, oldh);
+ if (mAnim != null) {
+ mAnim.cancel();
+ }
+ reset();
+ mAnim.start();
+ }
+
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mAnim.cancel();
+ }
+
+ @Override
+ public boolean isOpaque() {
+ return true;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent e) {
+ // we want to eat touch events ourselves if we're in warp speed
+ return (!(ROCKET_LAUNCHER && mManeuveringThrusters));
+ }
+
+ final Runnable mEngageWarp = new Runnable() {
+ @Override
+ public void run() {
+ mManeuveringThrusters = false;
+ }
+ };
+
+ public void resetWarpTimer() {
+ final Handler h = getHandler();
+ h.removeCallbacks(mEngageWarp);
+ h.postDelayed(mEngageWarp, 5000);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (!ROCKET_LAUNCHER) {
+ return true;
+ }
+
+ if (event.getAction() == MotionEvent.ACTION_DOWN) {
+ if (!mManeuveringThrusters) {
+ mManeuveringThrusters = true;
+ resetWarpTimer();
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+
+ DisplayMetrics metrics = new DisplayMetrics();
+ getWindowManager().getDefaultDisplay().getMetrics(metrics);
+ final int longside = metrics.widthPixels > metrics.heightPixels
+ ? metrics.widthPixels : metrics.heightPixels;
+
+ Board b = new Board(this, null);
+ setContentView(b, new ViewGroup.LayoutParams(longside, longside));
+ b.setX((metrics.widthPixels - longside) / 2);
+ b.setY((metrics.heightPixels - longside) / 2);
+ }
+
+ @Override
+ public void onUserInteraction() {
+ if (!ROCKET_LAUNCHER) {
+ finish();
+ }
+ }
+}
diff --git a/eggs/RocketLauncher/src/main/java/com/android/launcher2/RocketLauncherDream.kt b/eggs/RocketLauncher/src/main/java/com/android/launcher2/RocketLauncherDream.kt
new file mode 100644
index 00000000..fe265b50
--- /dev/null
+++ b/eggs/RocketLauncher/src/main/java/com/android/launcher2/RocketLauncherDream.kt
@@ -0,0 +1,24 @@
+package com.android.launcher2
+
+import android.service.dreams.DreamService
+import android.view.ViewGroup
+import kotlin.math.max
+
+class RocketLauncherDream : DreamService() {
+
+ override fun onAttachedToWindow() {
+ super.onAttachedToWindow()
+
+ setInteractive(false)
+ isFullscreen = true
+
+ val metrics = resources.displayMetrics
+ val longSide = max(metrics.widthPixels, metrics.heightPixels)
+
+ val b = RocketLauncher.Board(this, null)
+ setContentView(b, ViewGroup.LayoutParams(longSide, longSide))
+ b.x = ((metrics.widthPixels - longSide) / 2).toFloat()
+ b.y = ((metrics.heightPixels - longSide) / 2).toFloat()
+ }
+
+}
diff --git a/eggs/RocketLauncher/src/main/java/com/android/launcher2/Utils.kt b/eggs/RocketLauncher/src/main/java/com/android/launcher2/Utils.kt
new file mode 100644
index 00000000..93028536
--- /dev/null
+++ b/eggs/RocketLauncher/src/main/java/com/android/launcher2/Utils.kt
@@ -0,0 +1,60 @@
+package com.android.launcher2
+
+import android.Manifest
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.PackageManager
+import android.graphics.drawable.Drawable
+import androidx.annotation.RequiresPermission
+import com.dede.basic.provider.EasterEgg
+import com.dede.basic.requireDrawable
+
+object Utils {
+
+ @JvmStatic
+ @RequiresPermission(Manifest.permission.QUERY_ALL_PACKAGES)
+ fun queryAllPackagesComponentNameDrawableIcons(context: Context): HashMap {
+ val packageManager = context.packageManager
+ val list = packageManager
+ .getInstalledApplications(PackageManager.GET_ACTIVITIES)
+ val icons = HashMap()
+ for (info in list) {
+ if (!info.enabled) {
+ continue
+ }
+ val launchComponent = packageManager
+ .getLaunchIntentForPackage(info.packageName)?.component ?: continue
+ val drawable: Drawable
+ try {
+ drawable = packageManager.getApplicationIcon(info.packageName);
+ } catch (ignore: PackageManager.NameNotFoundException) {
+ continue
+ }
+ icons[launchComponent] = drawable;
+ }
+ return icons
+ }
+
+ @JvmStatic
+ fun convertComponentNameDrawableIcons(
+ context: Context,
+ easterEggs: List
+ ): HashMap {
+ val icons = HashMap()
+ for (egg in easterEggs) {
+ val drawable: Drawable = context.requireDrawable(egg.iconRes)
+ val aClass = egg.actionClass
+ val componentName: ComponentName = if (aClass != null) {
+ ComponentName(context, aClass)
+ } else {
+ createNotFoundComponent(context, egg.hashCode())
+ }
+ icons[componentName] = drawable
+ }
+ return icons
+ }
+
+ private fun createNotFoundComponent(context: Context, hash: Int): ComponentName {
+ return ComponentName(context, "NotFound%d".format(hash))
+ }
+}
\ No newline at end of file
diff --git a/eggs/RocketLauncher/src/main/res/drawable-hdpi/homescreen_small_blue.9.png b/eggs/RocketLauncher/src/main/res/drawable-hdpi/homescreen_small_blue.9.png
new file mode 100644
index 00000000..700fadc1
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/drawable-hdpi/homescreen_small_blue.9.png differ
diff --git a/eggs/RocketLauncher/src/main/res/drawable-hdpi/widget_resize_handle_bottom.png b/eggs/RocketLauncher/src/main/res/drawable-hdpi/widget_resize_handle_bottom.png
new file mode 100644
index 00000000..e039165f
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/drawable-hdpi/widget_resize_handle_bottom.png differ
diff --git a/eggs/RocketLauncher/src/main/res/drawable-mdpi/homescreen_small_blue.9.png b/eggs/RocketLauncher/src/main/res/drawable-mdpi/homescreen_small_blue.9.png
new file mode 100644
index 00000000..fb74449a
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/drawable-mdpi/homescreen_small_blue.9.png differ
diff --git a/eggs/RocketLauncher/src/main/res/drawable-mdpi/widget_resize_handle_bottom.png b/eggs/RocketLauncher/src/main/res/drawable-mdpi/widget_resize_handle_bottom.png
new file mode 100644
index 00000000..873d1bef
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/drawable-mdpi/widget_resize_handle_bottom.png differ
diff --git a/eggs/RocketLauncher/src/main/res/drawable-sw600dp-hdpi/widget_resize_handle_bottom.png b/eggs/RocketLauncher/src/main/res/drawable-sw600dp-hdpi/widget_resize_handle_bottom.png
new file mode 100644
index 00000000..045c15e2
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/drawable-sw600dp-hdpi/widget_resize_handle_bottom.png differ
diff --git a/eggs/RocketLauncher/src/main/res/drawable-sw600dp-mdpi/widget_resize_handle_bottom.png b/eggs/RocketLauncher/src/main/res/drawable-sw600dp-mdpi/widget_resize_handle_bottom.png
new file mode 100644
index 00000000..99ac1b20
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/drawable-sw600dp-mdpi/widget_resize_handle_bottom.png differ
diff --git a/eggs/RocketLauncher/src/main/res/drawable-xhdpi/homescreen_small_blue.9.png b/eggs/RocketLauncher/src/main/res/drawable-xhdpi/homescreen_small_blue.9.png
new file mode 100644
index 00000000..e678927f
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/drawable-xhdpi/homescreen_small_blue.9.png differ
diff --git a/eggs/RocketLauncher/src/main/res/drawable-xhdpi/widget_resize_handle_bottom.png b/eggs/RocketLauncher/src/main/res/drawable-xhdpi/widget_resize_handle_bottom.png
new file mode 100644
index 00000000..62882c83
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/drawable-xhdpi/widget_resize_handle_bottom.png differ
diff --git a/eggs/RocketLauncher/src/main/res/drawable/flying_icon_bg.xml b/eggs/RocketLauncher/src/main/res/drawable/flying_icon_bg.xml
new file mode 100644
index 00000000..6f5bf0bc
--- /dev/null
+++ b/eggs/RocketLauncher/src/main/res/drawable/flying_icon_bg.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
diff --git a/eggs/RocketLauncher/src/main/res/drawable/ic_rocket_launcher_foreground.xml b/eggs/RocketLauncher/src/main/res/drawable/ic_rocket_launcher_foreground.xml
new file mode 100644
index 00000000..27120097
--- /dev/null
+++ b/eggs/RocketLauncher/src/main/res/drawable/ic_rocket_launcher_foreground.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
diff --git a/eggs/RocketLauncher/src/main/res/mipmap-anydpi-v26/ic_rocket_launcher.xml b/eggs/RocketLauncher/src/main/res/mipmap-anydpi-v26/ic_rocket_launcher.xml
new file mode 100644
index 00000000..c5cb236b
--- /dev/null
+++ b/eggs/RocketLauncher/src/main/res/mipmap-anydpi-v26/ic_rocket_launcher.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eggs/RocketLauncher/src/main/res/mipmap-anydpi-v26/ic_rocket_launcher_round.xml b/eggs/RocketLauncher/src/main/res/mipmap-anydpi-v26/ic_rocket_launcher_round.xml
new file mode 100644
index 00000000..c5cb236b
--- /dev/null
+++ b/eggs/RocketLauncher/src/main/res/mipmap-anydpi-v26/ic_rocket_launcher_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eggs/RocketLauncher/src/main/res/mipmap-hdpi/ic_rocket_launcher.png b/eggs/RocketLauncher/src/main/res/mipmap-hdpi/ic_rocket_launcher.png
new file mode 100644
index 00000000..fbee96a4
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/mipmap-hdpi/ic_rocket_launcher.png differ
diff --git a/eggs/RocketLauncher/src/main/res/mipmap-hdpi/ic_rocket_launcher_round.png b/eggs/RocketLauncher/src/main/res/mipmap-hdpi/ic_rocket_launcher_round.png
new file mode 100644
index 00000000..37dbcd7e
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/mipmap-hdpi/ic_rocket_launcher_round.png differ
diff --git a/eggs/RocketLauncher/src/main/res/mipmap-mdpi/ic_rocket_launcher.png b/eggs/RocketLauncher/src/main/res/mipmap-mdpi/ic_rocket_launcher.png
new file mode 100644
index 00000000..f4367bce
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/mipmap-mdpi/ic_rocket_launcher.png differ
diff --git a/eggs/RocketLauncher/src/main/res/mipmap-mdpi/ic_rocket_launcher_round.png b/eggs/RocketLauncher/src/main/res/mipmap-mdpi/ic_rocket_launcher_round.png
new file mode 100644
index 00000000..1a931240
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/mipmap-mdpi/ic_rocket_launcher_round.png differ
diff --git a/eggs/RocketLauncher/src/main/res/mipmap-xhdpi/ic_rocket_launcher.png b/eggs/RocketLauncher/src/main/res/mipmap-xhdpi/ic_rocket_launcher.png
new file mode 100644
index 00000000..fef19402
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/mipmap-xhdpi/ic_rocket_launcher.png differ
diff --git a/eggs/RocketLauncher/src/main/res/mipmap-xhdpi/ic_rocket_launcher_round.png b/eggs/RocketLauncher/src/main/res/mipmap-xhdpi/ic_rocket_launcher_round.png
new file mode 100644
index 00000000..64d329b9
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/mipmap-xhdpi/ic_rocket_launcher_round.png differ
diff --git a/eggs/RocketLauncher/src/main/res/mipmap-xxhdpi/ic_rocket_launcher.png b/eggs/RocketLauncher/src/main/res/mipmap-xxhdpi/ic_rocket_launcher.png
new file mode 100644
index 00000000..86a315ac
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/mipmap-xxhdpi/ic_rocket_launcher.png differ
diff --git a/eggs/RocketLauncher/src/main/res/mipmap-xxhdpi/ic_rocket_launcher_round.png b/eggs/RocketLauncher/src/main/res/mipmap-xxhdpi/ic_rocket_launcher_round.png
new file mode 100644
index 00000000..200c107e
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/mipmap-xxhdpi/ic_rocket_launcher_round.png differ
diff --git a/eggs/RocketLauncher/src/main/res/mipmap-xxxhdpi/ic_rocket_launcher.png b/eggs/RocketLauncher/src/main/res/mipmap-xxxhdpi/ic_rocket_launcher.png
new file mode 100644
index 00000000..41f339f2
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/mipmap-xxxhdpi/ic_rocket_launcher.png differ
diff --git a/eggs/RocketLauncher/src/main/res/mipmap-xxxhdpi/ic_rocket_launcher_round.png b/eggs/RocketLauncher/src/main/res/mipmap-xxxhdpi/ic_rocket_launcher_round.png
new file mode 100644
index 00000000..ca24a4b8
Binary files /dev/null and b/eggs/RocketLauncher/src/main/res/mipmap-xxxhdpi/ic_rocket_launcher_round.png differ
diff --git a/eggs/RocketLauncher/src/main/res/values/ic_rocket_launcher_background.xml b/eggs/RocketLauncher/src/main/res/values/ic_rocket_launcher_background.xml
new file mode 100644
index 00000000..7bdaf0fc
--- /dev/null
+++ b/eggs/RocketLauncher/src/main/res/values/ic_rocket_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #1B1E22
+
\ No newline at end of file
diff --git a/eggs/RocketLauncher/src/main/res/values/strings.xml b/eggs/RocketLauncher/src/main/res/values/strings.xml
new file mode 100644
index 00000000..5708c708
--- /dev/null
+++ b/eggs/RocketLauncher/src/main/res/values/strings.xml
@@ -0,0 +1,6 @@
+
+
+
+ Rocket Launcher
+ Easter Egg of the Launcher in Android 4.0 (Ice Cream Sandwich)
+
diff --git a/fastlane/metadata/android/en-US/changelogs/56.txt b/fastlane/metadata/android/en-US/changelogs/56.txt
index 6c786699..53642ed3 100644
--- a/fastlane/metadata/android/en-US/changelogs/56.txt
+++ b/fastlane/metadata/android/en-US/changelogs/56.txt
@@ -1,3 +1,4 @@
+- Add Rocket Launcher Easter Egg
- Optimize the setting of theme logic
- Optimize APK file size
- Project modularization
diff --git a/fastlane/metadata/android/zh-CN/changelogs/56.txt b/fastlane/metadata/android/zh-CN/changelogs/56.txt
index 9ded9671..d1cb6279 100644
--- a/fastlane/metadata/android/zh-CN/changelogs/56.txt
+++ b/fastlane/metadata/android/zh-CN/changelogs/56.txt
@@ -1,3 +1,4 @@
+- 添加 Rocket Launcher 彩蛋
- 优化设置主题逻辑
- 优化 APK 文件大小
- 项目模块化
diff --git a/feature/embedding-splits/src/main/res/xml/split_configuration.xml b/feature/embedding-splits/src/main/res/xml/split_configuration.xml
index f6cd61c8..44d2f571 100644
--- a/feature/embedding-splits/src/main/res/xml/split_configuration.xml
+++ b/feature/embedding-splits/src/main/res/xml/split_configuration.xml
@@ -51,6 +51,9 @@
+