diff --git a/core/java/com/android/internal/app/PlatLogoActivity.java b/core/java/com/android/internal/app/PlatLogoActivity.java
index ef2fd0d7401de..bddd8261746b6 100644
--- a/core/java/com/android/internal/app/PlatLogoActivity.java
+++ b/core/java/com/android/internal/app/PlatLogoActivity.java
@@ -53,7 +53,7 @@ import android.widget.FrameLayout;
import android.widget.ImageView;
public class PlatLogoActivity extends Activity {
- public static final boolean REVEAL_THE_NAME = true;
+ public static final boolean REVEAL_THE_NAME = false;
FrameLayout mLayout;
int mTapCount;
@@ -112,7 +112,6 @@ public class PlatLogoActivity extends Activity {
ObjectAnimator.ofInt(overlay, "alpha", 0, 255)
.setDuration(500)
.start();
- return true;
}
final ContentResolver cr = getContentResolver();
diff --git a/packages/EasterEgg/Android.mk b/packages/EasterEgg/Android.mk
new file mode 100644
index 0000000000000..df081f4091d97
--- /dev/null
+++ b/packages/EasterEgg/Android.mk
@@ -0,0 +1,21 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-v4 \
+ android-support-v13 \
+ android-support-v7-recyclerview \
+ android-support-v7-preference \
+ android-support-v7-appcompat \
+ android-support-v14-preference \
+ jsr305
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := EasterEgg
+LOCAL_CERTIFICATE := platform
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/packages/EasterEgg/AndroidManifest.xml b/packages/EasterEgg/AndroidManifest.xml
new file mode 100644
index 0000000000000..50e8b5ca7d2bd
--- /dev/null
+++ b/packages/EasterEgg/AndroidManifest.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/back.xml b/packages/EasterEgg/res/drawable/back.xml
new file mode 100644
index 0000000000000..b55d65cdf76d8
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/back.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/belly.xml b/packages/EasterEgg/res/drawable/belly.xml
new file mode 100644
index 0000000000000..8b0e9afac4632
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/belly.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/body.xml b/packages/EasterEgg/res/drawable/body.xml
new file mode 100644
index 0000000000000..86087209eff5f
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/body.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/bowtie.xml b/packages/EasterEgg/res/drawable/bowtie.xml
new file mode 100644
index 0000000000000..33fa9216712fb
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/bowtie.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/cap.xml b/packages/EasterEgg/res/drawable/cap.xml
new file mode 100644
index 0000000000000..d8b4cc58a261f
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/cap.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/collar.xml b/packages/EasterEgg/res/drawable/collar.xml
new file mode 100644
index 0000000000000..6c0d90a9bf224
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/collar.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/face_spot.xml b/packages/EasterEgg/res/drawable/face_spot.xml
new file mode 100644
index 0000000000000..a89fb4fdaadd5
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/face_spot.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/food_bacon.xml b/packages/EasterEgg/res/drawable/food_bacon.xml
new file mode 100644
index 0000000000000..1df325c7b2f71
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/food_bacon.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/food_bits.xml b/packages/EasterEgg/res/drawable/food_bits.xml
new file mode 100644
index 0000000000000..1b2bb6f369470
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/food_bits.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/food_dish.xml b/packages/EasterEgg/res/drawable/food_dish.xml
new file mode 100644
index 0000000000000..3fff6a90fad2b
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/food_dish.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/food_donut.xml b/packages/EasterEgg/res/drawable/food_donut.xml
new file mode 100644
index 0000000000000..eaf831ea560c0
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/food_donut.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/food_sysuituna.xml b/packages/EasterEgg/res/drawable/food_sysuituna.xml
new file mode 100644
index 0000000000000..28cf4a2c76836
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/food_sysuituna.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/foot1.xml b/packages/EasterEgg/res/drawable/foot1.xml
new file mode 100644
index 0000000000000..0d9085998a182
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/foot1.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/foot2.xml b/packages/EasterEgg/res/drawable/foot2.xml
new file mode 100644
index 0000000000000..364ba0cd861c4
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/foot2.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/foot3.xml b/packages/EasterEgg/res/drawable/foot3.xml
new file mode 100644
index 0000000000000..e3a512a2568d9
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/foot3.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/foot4.xml b/packages/EasterEgg/res/drawable/foot4.xml
new file mode 100644
index 0000000000000..66b78fa266490
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/foot4.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/head.xml b/packages/EasterEgg/res/drawable/head.xml
new file mode 100644
index 0000000000000..df600a8613cd6
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/head.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/ic_close.xml b/packages/EasterEgg/res/drawable/ic_close.xml
new file mode 100644
index 0000000000000..60ea36b11fccd
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/ic_close.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/ic_share.xml b/packages/EasterEgg/res/drawable/ic_share.xml
new file mode 100644
index 0000000000000..8cebc7ed46dea
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/ic_share.xml
@@ -0,0 +1,24 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/icon.xml b/packages/EasterEgg/res/drawable/icon.xml
new file mode 100644
index 0000000000000..5e08fcbf8f524
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/icon.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/left_ear.xml b/packages/EasterEgg/res/drawable/left_ear.xml
new file mode 100644
index 0000000000000..2b98736df039b
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/left_ear.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/left_ear_inside.xml b/packages/EasterEgg/res/drawable/left_ear_inside.xml
new file mode 100644
index 0000000000000..1d947edc31e21
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/left_ear_inside.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/left_eye.xml b/packages/EasterEgg/res/drawable/left_eye.xml
new file mode 100644
index 0000000000000..4dde1b661393c
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/left_eye.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/leg1.xml b/packages/EasterEgg/res/drawable/leg1.xml
new file mode 100644
index 0000000000000..625733318e72b
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/leg1.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/leg2.xml b/packages/EasterEgg/res/drawable/leg2.xml
new file mode 100644
index 0000000000000..73352f69e80a9
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/leg2.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/leg2_shadow.xml b/packages/EasterEgg/res/drawable/leg2_shadow.xml
new file mode 100644
index 0000000000000..77f4893194fe3
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/leg2_shadow.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/leg3.xml b/packages/EasterEgg/res/drawable/leg3.xml
new file mode 100644
index 0000000000000..53dea5c2becf4
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/leg3.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/leg4.xml b/packages/EasterEgg/res/drawable/leg4.xml
new file mode 100644
index 0000000000000..f2ce73e195a29
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/leg4.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/mouth.xml b/packages/EasterEgg/res/drawable/mouth.xml
new file mode 100644
index 0000000000000..ddcf2e82f976b
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/mouth.xml
@@ -0,0 +1,27 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/nose.xml b/packages/EasterEgg/res/drawable/nose.xml
new file mode 100644
index 0000000000000..d403cd1baadfe
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/nose.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/right_ear.xml b/packages/EasterEgg/res/drawable/right_ear.xml
new file mode 100644
index 0000000000000..b9fb4d1c74708
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/right_ear.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/right_ear_inside.xml b/packages/EasterEgg/res/drawable/right_ear_inside.xml
new file mode 100644
index 0000000000000..86b6e3428d1f3
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/right_ear_inside.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/right_eye.xml b/packages/EasterEgg/res/drawable/right_eye.xml
new file mode 100644
index 0000000000000..a1871a62c25bb
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/right_eye.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/stat_icon.xml b/packages/EasterEgg/res/drawable/stat_icon.xml
new file mode 100644
index 0000000000000..608cb2017c3f0
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/stat_icon.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/tail.xml b/packages/EasterEgg/res/drawable/tail.xml
new file mode 100644
index 0000000000000..0cca23c3e16cb
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/tail.xml
@@ -0,0 +1,26 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/tail_cap.xml b/packages/EasterEgg/res/drawable/tail_cap.xml
new file mode 100644
index 0000000000000..b82f6f9b478ad
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/tail_cap.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/drawable/tail_shadow.xml b/packages/EasterEgg/res/drawable/tail_shadow.xml
new file mode 100644
index 0000000000000..bb1ff12b3afed
--- /dev/null
+++ b/packages/EasterEgg/res/drawable/tail_shadow.xml
@@ -0,0 +1,22 @@
+
+
+
+
diff --git a/packages/EasterEgg/res/layout/cat_view.xml b/packages/EasterEgg/res/layout/cat_view.xml
new file mode 100644
index 0000000000000..82ced2f240b53
--- /dev/null
+++ b/packages/EasterEgg/res/layout/cat_view.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/EasterEgg/res/layout/edit_text.xml b/packages/EasterEgg/res/layout/edit_text.xml
new file mode 100644
index 0000000000000..9f7ac802bad4a
--- /dev/null
+++ b/packages/EasterEgg/res/layout/edit_text.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/EasterEgg/res/layout/food_layout.xml b/packages/EasterEgg/res/layout/food_layout.xml
new file mode 100644
index 0000000000000..d0ca0c8899aae
--- /dev/null
+++ b/packages/EasterEgg/res/layout/food_layout.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/packages/EasterEgg/res/layout/neko_activity.xml b/packages/EasterEgg/res/layout/neko_activity.xml
new file mode 100644
index 0000000000000..21a4600bae403
--- /dev/null
+++ b/packages/EasterEgg/res/layout/neko_activity.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/EasterEgg/res/values/strings.xml b/packages/EasterEgg/res/values/strings.xml
new file mode 100644
index 0000000000000..074a86430284a
--- /dev/null
+++ b/packages/EasterEgg/res/values/strings.xml
@@ -0,0 +1,52 @@
+
+
+
+ Android Easter Egg
+ Android Neko
+ \????
+ A cat is here.
+ Cat #%s
+ Cats
+
+ - Empty dish
+ - Bits
+ - Fish
+ - Bacon
+ - Treat
+
+
+ - @drawable/food_dish
+ - @drawable/food_bits
+ - @drawable/food_sysuituna
+ - @drawable/food_bacon
+ - @drawable/food_donut
+
+
+ - 0
+ - 15
+ - 30
+ - 60
+ - 120
+
+
+ - 0
+ - 5
+ - 25
+ - 50
+ - 75
+
+
diff --git a/packages/EasterEgg/src/com/android/egg/neko/Cat.java b/packages/EasterEgg/src/com/android/egg/neko/Cat.java
new file mode 100644
index 0000000000000..525b035989f9a
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/neko/Cat.java
@@ -0,0 +1,373 @@
+/*
+ * Copyright (C) 2016 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.egg.neko;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.*;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.Bundle;
+
+import java.util.Random;
+import java.util.concurrent.ThreadLocalRandom;
+
+import com.android.egg.R;
+
+public class Cat extends Drawable {
+ private Random mNotSoRandom;
+ private Bitmap mBitmap;
+ private long mSeed;
+ private String mName;
+ private int mBodyColor;
+
+ private synchronized Random notSoRandom(long seed) {
+ if (mNotSoRandom == null) {
+ mNotSoRandom = new Random();
+ mNotSoRandom.setSeed(seed);
+ }
+ return mNotSoRandom;
+ }
+
+ public static final float frandrange(Random r, float a, float b) {
+ return (b-a)*r.nextFloat() + a;
+ }
+
+ public static final Object choose(Random r, Object...l) {
+ return l[r.nextInt(l.length)];
+ }
+
+ public static final int chooseP(Random r, int[] a) {
+ int pct = r.nextInt(1000);
+ final int stop = a.length-2;
+ int i=0;
+ while (i> 16;
+ final int g = (color & 0x00FF00) >> 8;
+ final int b = color & 0x0000FF;
+ return (r + g + b) < 0x80;
+ }
+
+ public Cat(Context context, long seed) {
+ D = new CatParts(context);
+ mSeed = seed;
+
+ setName(context.getString(R.string.default_cat_name,
+ String.valueOf(mSeed).substring(0, 3)));
+
+ final Random nsr = notSoRandom(seed);
+
+ // body color
+ mBodyColor = chooseP(nsr, P_BODY_COLORS);
+ if (mBodyColor == 0) mBodyColor = Color.HSVToColor(new float[] {
+ nsr.nextFloat()*360f, frandrange(nsr,0.5f,1f), frandrange(nsr,0.5f, 1f)});
+
+ tint(mBodyColor, D.body, D.head, D.leg1, D.leg2, D.leg3, D.leg4, D.tail,
+ D.leftEar, D.rightEar, D.foot1, D.foot2, D.foot3, D.foot4, D.tailCap);
+ tint(0x20000000, D.leg2Shadow, D.tailShadow);
+ if (isDark(mBodyColor)) {
+ tint(0xFFFFFFFF, D.leftEye, D.rightEye, D.mouth, D.nose);
+ }
+ tint(isDark(mBodyColor) ? 0xFFEF9A9A : 0x20D50000, D.leftEarInside, D.rightEarInside);
+
+ tint(chooseP(nsr, P_BELLY_COLORS), D.belly);
+ tint(chooseP(nsr, P_BELLY_COLORS), D.back);
+ final int faceColor = chooseP(nsr, P_BELLY_COLORS);
+ tint(faceColor, D.faceSpot);
+ if (!isDark(faceColor)) {
+ tint(0xFF000000, D.mouth, D.nose);
+ }
+
+ if (nsr.nextFloat() < 0.25f) {
+ tint(0xFFFFFFFF, D.foot1, D.foot2, D.foot3, D.foot4);
+ } else {
+ if (nsr.nextFloat() < 0.25f) {
+ tint(0xFFFFFFFF, D.foot1, D.foot2);
+ } else if (nsr.nextFloat() < 0.25f) {
+ tint(0xFFFFFFFF, D.foot3, D.foot4);
+ } else if (nsr.nextFloat() < 0.1f) {
+ tint(0xFFFFFFFF, (Drawable) choose(nsr, D.foot1, D.foot2, D.foot3, D.foot4));
+ }
+ }
+
+ tint(nsr.nextFloat() < 0.333f ? 0xFFFFFFFF : mBodyColor, D.tailCap);
+
+ final int capColor = chooseP(nsr, isDark(mBodyColor) ? P_LIGHT_SPOT_COLORS : P_DARK_SPOT_COLORS);
+ tint(capColor, D.cap);
+ //tint(chooseP(nsr, isDark(bodyColor) ? P_LIGHT_SPOT_COLORS : P_DARK_SPOT_COLORS), D.nose);
+
+ final int collarColor = chooseP(nsr, P_COLLAR_COLORS);
+ tint(collarColor, D.collar);
+ tint((nsr.nextFloat() < 0.1f) ? collarColor : 0, D.bowtie);
+ }
+
+ public static Cat create(Context context) {
+ return new Cat(context, Math.abs(ThreadLocalRandom.current().nextInt()));
+ }
+
+ public Notification.Builder buildNotification(Context context) {
+ final Bundle extras = new Bundle();
+ extras.putString("android.substName", context.getString(R.string.notification_name));
+ final Intent intent = new Intent(Intent.ACTION_MAIN)
+ .setClass(context, NekoLand.class)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ return new Notification.Builder(context)
+ .setSmallIcon(Icon.createWithResource(context, R.drawable.stat_icon))
+ .setLargeIcon(createLargeIcon(context))
+ .setColor(getBodyColor())
+ .setPriority(Notification.PRIORITY_LOW)
+ .setContentTitle(context.getString(R.string.notification_title))
+ .setShowWhen(true)
+ .setCategory(Notification.CATEGORY_STATUS)
+ .setContentText(getName())
+ .setContentIntent(PendingIntent.getActivity(context, 0, intent, 0))
+ .setAutoCancel(true)
+ .addExtras(extras);
+ }
+
+ public long getSeed() {
+ return mSeed;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ final int w = Math.min(canvas.getWidth(), canvas.getHeight());
+ final int h = w;
+
+ if (mBitmap == null || mBitmap.getWidth() != w || mBitmap.getHeight() != h) {
+ mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+ final Canvas bitCanvas = new Canvas(mBitmap);
+ slowDraw(bitCanvas, 0, 0, w, h);
+ }
+ canvas.drawBitmap(mBitmap, 0, 0, null);
+ }
+
+ private void slowDraw(Canvas canvas, int x, int y, int w, int h) {
+ for (int i = 0; i < D.drawingOrder.length; i++) {
+ final Drawable d = D.drawingOrder[i];
+ if (d != null) {
+ d.setBounds(x, y, x+w, y+h);
+ d.draw(canvas);
+ }
+ }
+
+ }
+
+ public Bitmap createBitmap(int w, int h) {
+ if (mBitmap != null && mBitmap.getWidth() == w && mBitmap.getHeight() == h) {
+ return mBitmap.copy(mBitmap.getConfig(), true);
+ }
+ Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+ slowDraw(new Canvas(result), 0, 0, w, h);
+ return result;
+ }
+
+ public Icon createLargeIcon(Context context) {
+ final Resources res = context.getResources();
+ final int w = res.getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
+ final int h = res.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
+
+ Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(result);
+ final Paint pt = new Paint();
+ float[] hsv = new float[3];
+ Color.colorToHSV(mBodyColor, hsv);
+ hsv[2] = (hsv[2]>0.5f)
+ ? (hsv[2] - 0.25f)
+ : (hsv[2] + 0.25f);
+ pt.setColor(Color.HSVToColor(hsv));
+ float r = w/2;
+ canvas.drawCircle(r, r, r, pt);
+ int m = w/10;
+
+ slowDraw(canvas, m, m, w-m-m, h-m-m);
+
+ return Icon.createWithBitmap(result);
+ }
+
+ @Override
+ public void setAlpha(int i) {
+
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSLUCENT;
+ }
+
+ public String getName() {
+ return mName;
+ }
+
+ public void setName(String name) {
+ this.mName = name;
+ }
+
+ public int getBodyColor() {
+ return mBodyColor;
+ }
+
+ public static class CatParts {
+ public Drawable leftEar;
+ public Drawable rightEar;
+ public Drawable rightEarInside;
+ public Drawable leftEarInside;
+ public Drawable head;
+ public Drawable faceSpot;
+ public Drawable cap;
+ public Drawable mouth;
+ public Drawable body;
+ public Drawable foot1;
+ public Drawable leg1;
+ public Drawable foot2;
+ public Drawable leg2;
+ public Drawable foot3;
+ public Drawable leg3;
+ public Drawable foot4;
+ public Drawable leg4;
+ public Drawable tail;
+ public Drawable leg2Shadow;
+ public Drawable tailShadow;
+ public Drawable tailCap;
+ public Drawable belly;
+ public Drawable back;
+ public Drawable rightEye;
+ public Drawable leftEye;
+ public Drawable nose;
+ public Drawable bowtie;
+ public Drawable collar;
+ public Drawable[] drawingOrder;
+
+ public CatParts(Context context) {
+ body = context.getDrawable(R.drawable.body);
+ head = context.getDrawable(R.drawable.head);
+ leg1 = context.getDrawable(R.drawable.leg1);
+ leg2 = context.getDrawable(R.drawable.leg2);
+ leg3 = context.getDrawable(R.drawable.leg3);
+ leg4 = context.getDrawable(R.drawable.leg4);
+ tail = context.getDrawable(R.drawable.tail);
+ leftEar = context.getDrawable(R.drawable.left_ear);
+ rightEar = context.getDrawable(R.drawable.right_ear);
+ rightEarInside = context.getDrawable(R.drawable.right_ear_inside);
+ leftEarInside = context.getDrawable(R.drawable.left_ear_inside);
+ faceSpot = context.getDrawable(R.drawable.face_spot);
+ cap = context.getDrawable(R.drawable.cap);
+ mouth = context.getDrawable(R.drawable.mouth);
+ foot4 = context.getDrawable(R.drawable.foot4);
+ foot3 = context.getDrawable(R.drawable.foot3);
+ foot1 = context.getDrawable(R.drawable.foot1);
+ foot2 = context.getDrawable(R.drawable.foot2);
+ leg2Shadow = context.getDrawable(R.drawable.leg2_shadow);
+ tailShadow = context.getDrawable(R.drawable.tail_shadow);
+ tailCap = context.getDrawable(R.drawable.tail_cap);
+ belly = context.getDrawable(R.drawable.belly);
+ back = context.getDrawable(R.drawable.back);
+ rightEye = context.getDrawable(R.drawable.right_eye);
+ leftEye = context.getDrawable(R.drawable.left_eye);
+ nose = context.getDrawable(R.drawable.nose);
+ collar = context.getDrawable(R.drawable.collar);
+ bowtie = context.getDrawable(R.drawable.bowtie);
+ drawingOrder = getDrawingOrder();
+ }
+ private Drawable[] getDrawingOrder() {
+ return new Drawable[] {
+ collar,
+ leftEar, leftEarInside, rightEar, rightEarInside,
+ head,
+ faceSpot,
+ cap,
+ leftEye, rightEye,
+ nose, mouth,
+ tail, tailCap, tailShadow,
+ foot1, leg1,
+ foot2, leg2,
+ foot3, leg3,
+ foot4, leg4,
+ leg2Shadow,
+ body, belly,
+ bowtie
+ };
+ }
+ }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/neko/Food.java b/packages/EasterEgg/src/com/android/egg/neko/Food.java
new file mode 100644
index 0000000000000..5c0f12e2639f2
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/neko/Food.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 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.egg.neko;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+
+import com.android.egg.R;
+
+public class Food {
+ private final int mType;
+
+ private static int[] sIcons;
+ private static String[] sNames;
+
+ public Food(int type) {
+ mType = type;
+ }
+
+ public Icon getIcon(Context context) {
+ if (sIcons == null) {
+ TypedArray icons = context.getResources().obtainTypedArray(R.array.food_icons);
+ sIcons = new int[icons.length()];
+ for (int i = 0; i < sIcons.length; i++) {
+ sIcons[i] = icons.getResourceId(i, 0);
+ }
+ icons.recycle();
+ }
+ return Icon.createWithResource(context, sIcons[mType]);
+ }
+
+ public String getName(Context context) {
+ if (sNames == null) {
+ sNames = context.getResources().getStringArray(R.array.food_names);
+ }
+ return sNames[mType];
+ }
+
+ public long getInterval(Context context) {
+ return context.getResources().getIntArray(R.array.food_intervals)[mType];
+ }
+
+ public int getType() {
+ return mType;
+ }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/neko/NekoActivationActivity.java b/packages/EasterEgg/src/com/android/egg/neko/NekoActivationActivity.java
new file mode 100644
index 0000000000000..8fbab99d8884a
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/neko/NekoActivationActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 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.egg.neko;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.pm.PackageManager;
+import android.util.Log;
+
+public class NekoActivationActivity extends Activity {
+ @Override
+ public void onStart() {
+ final PackageManager pm = getPackageManager();
+ final ComponentName cn = new ComponentName(this, NekoTile.class);
+ if (pm.getComponentEnabledSetting(cn) == PackageManager.COMPONENT_ENABLED_STATE_ENABLED) {
+ if (NekoLand.DEBUG) {
+ Log.v("Neko", "Disabling tile.");
+ }
+ pm.setComponentEnabledSetting(cn, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 0);
+ } else {
+ if (NekoLand.DEBUG) {
+ Log.v("Neko", "Enabling tile.");
+ }
+ pm.setComponentEnabledSetting(cn, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+ }
+ finish();
+ }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/neko/NekoDialog.java b/packages/EasterEgg/src/com/android/egg/neko/NekoDialog.java
new file mode 100644
index 0000000000000..a2ffd3e21887a
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/neko/NekoDialog.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2016 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.egg.neko;
+
+import android.support.annotation.NonNull;
+import android.app.Dialog;
+import android.content.Context;
+import android.support.v7.widget.GridLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.egg.R;
+
+import java.util.ArrayList;
+
+public class NekoDialog extends Dialog {
+
+ private final Adapter mAdapter;
+
+ public NekoDialog(@NonNull Context context) {
+ super(context, android.R.style.Theme_Material_Dialog_NoActionBar);
+ RecyclerView view = new RecyclerView(getContext());
+ mAdapter = new Adapter(getContext());
+ view.setLayoutManager(new GridLayoutManager(getContext(), 2));
+ view.setAdapter(mAdapter);
+ final float dp = context.getResources().getDisplayMetrics().density;
+ final int pad = (int)(16*dp);
+ view.setPadding(pad, pad, pad, pad);
+ setContentView(view);
+ }
+
+ private void onFoodSelected(Food food) {
+ PrefState prefs = new PrefState(getContext());
+ int currentState = prefs.getFoodState();
+ if (currentState == 0 && food.getType() != 0) {
+ NekoService.registerJob(getContext(), food.getInterval(getContext()));
+ }
+ prefs.setFoodState(food.getType());
+ dismiss();
+ }
+
+ private class Adapter extends RecyclerView.Adapter {
+
+ private final Context mContext;
+ private final ArrayList mFoods = new ArrayList<>();
+
+ public Adapter(Context context) {
+ mContext = context;
+ int[] foods = context.getResources().getIntArray(R.array.food_names);
+ // skip food 0, you can't choose it
+ for (int i=1; i {
+
+ private Cat[] mCats;
+
+ public void setCats(Cat[] cats) {
+ mCats = cats;
+ notifyDataSetChanged();
+ }
+
+ @Override
+ public CatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new CatHolder(LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.cat_view, parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(final CatHolder holder, int position) {
+ Context context = holder.itemView.getContext();
+ holder.imageView.setImageIcon(mCats[position].createLargeIcon(context));
+ holder.textView.setText(mCats[position].getName());
+ holder.itemView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onCatClick(mCats[holder.getAdapterPosition()]);
+ }
+ });
+ holder.itemView.setOnLongClickListener(new OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ holder.contextGroup.removeCallbacks((Runnable) holder.contextGroup.getTag());
+ holder.contextGroup.setVisibility(View.VISIBLE);
+ Runnable hideAction = new Runnable() {
+ @Override
+ public void run() {
+ holder.contextGroup.setVisibility(View.INVISIBLE);
+ }
+ };
+ holder.contextGroup.setTag(hideAction);
+ holder.contextGroup.postDelayed(hideAction, 5000);
+ return true;
+ }
+ });
+ holder.delete.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ holder.contextGroup.setVisibility(View.INVISIBLE);
+ holder.contextGroup.removeCallbacks((Runnable) holder.contextGroup.getTag());
+ onCatRemove(mCats[holder.getAdapterPosition()]);
+ }
+ });
+ holder.share.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Cat cat = mCats[holder.getAdapterPosition()];
+ if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ != PackageManager.PERMISSION_GRANTED) {
+ mPendingShareCat = cat;
+ requestPermissions(
+ new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
+ STORAGE_PERM_REQUEST);
+ return;
+ }
+ shareCat(cat);
+ }
+ });
+ }
+
+ @Override
+ public int getItemCount() {
+ return mCats.length;
+ }
+ }
+
+ private void shareCat(Cat cat) {
+ final File dir = new File(
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
+ getString(R.string.directory_name));
+ if (!dir.exists() && !dir.mkdirs()) {
+ Log.e("NekoLand", "save: error: can't create Pictures directory");
+ return;
+ }
+ final File png = new File(dir, cat.getName().replaceAll("[/ #:]+", "_") + ".png");
+ Bitmap bitmap = cat.createBitmap(512, 512);
+ if (bitmap != null) {
+ try {
+ OutputStream os = new FileOutputStream(png);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 0, os);
+ os.close();
+ MediaScannerConnection.scanFile(
+ this,
+ new String[] {png.toString()},
+ new String[] {"image/png"},
+ null);
+ Uri uri = Uri.fromFile(png);
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.putExtra(Intent.EXTRA_STREAM, uri);
+ intent.putExtra(Intent.EXTRA_SUBJECT, cat.getName());
+ intent.setType("image/png");
+ startActivity(Intent.createChooser(intent, null));
+ } catch (IOException e) {
+ Log.e("NekoLand", "save: error: " + e);
+ }
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode,
+ String permissions[], int[] grantResults) {
+ if (requestCode == STORAGE_PERM_REQUEST) {
+ if (mPendingShareCat != null) {
+ shareCat(mPendingShareCat);
+ mPendingShareCat = null;
+ }
+ }
+ }
+
+ private static class CatHolder extends RecyclerView.ViewHolder {
+ private final ImageView imageView;
+ private final TextView textView;
+ private final View contextGroup;
+ private final View delete;
+ private final View share;
+
+ public CatHolder(View itemView) {
+ super(itemView);
+ imageView = (ImageView) itemView.findViewById(android.R.id.icon);
+ textView = (TextView) itemView.findViewById(android.R.id.title);
+ contextGroup = itemView.findViewById(R.id.contextGroup);
+ delete = itemView.findViewById(android.R.id.closeButton);
+ share = itemView.findViewById(android.R.id.shareText);
+ }
+ }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/neko/NekoLockedActivity.java b/packages/EasterEgg/src/com/android/egg/neko/NekoLockedActivity.java
new file mode 100644
index 0000000000000..5f01da879ebbb
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/neko/NekoLockedActivity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 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.egg.neko;
+
+import android.support.annotation.Nullable;
+import android.app.Activity;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public class NekoLockedActivity extends Activity implements OnDismissListener {
+
+ private NekoDialog mDialog;
+
+ @Override
+ public void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+
+ mDialog = new NekoDialog(this);
+ mDialog.setOnDismissListener(this);
+ mDialog.show();
+ }
+
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ finish();
+ }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/neko/NekoService.java b/packages/EasterEgg/src/com/android/egg/neko/NekoService.java
new file mode 100644
index 0000000000000..1ee385136f472
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/neko/NekoService.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 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.egg.neko;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+import java.util.List;
+import android.util.Log;
+
+import com.android.egg.R;
+
+import java.util.Random;
+
+public class NekoService extends JobService {
+
+ private static final String TAG = "NekoService";
+
+ public static int JOB_ID = 42;
+
+ public static int CAT_NOTIFICATION = 1;
+
+ public static float CAT_CAPTURE_PROB = 1.0f; // generous
+
+ public static long SECONDS = 1000;
+ public static long MINUTES = 60 * SECONDS;
+
+ public static long INTERVAL_FLEX = 5 * MINUTES;
+
+ public static float INTERVAL_JITTER_FRAC = 0.25f;
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ Log.v(TAG, "Starting job: " + String.valueOf(params));
+
+ NotificationManager noman = getSystemService(NotificationManager.class);
+ if (NekoLand.DEBUG_NOTIFICATIONS) {
+ final Bundle extras = new Bundle();
+ extras.putString("android.substName", getString(R.string.notification_name));
+ final int size = getResources()
+ .getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
+ final Cat cat = Cat.create(this);
+ final Notification.Builder builder
+ = cat.buildNotification(this)
+ .setContentTitle("DEBUG")
+ .setContentText("Ran job: " + params);
+ noman.notify(1, builder.build());
+ }
+
+ final PrefState prefs = new PrefState(this);
+ int food = prefs.getFoodState();
+ if (food != 0) {
+ prefs.setFoodState(0); // nom
+ final Random rng = new Random();
+ if (rng.nextFloat() <= CAT_CAPTURE_PROB) {
+ Cat cat;
+ List cats = prefs.getCats();
+ final int[] probs = getResources().getIntArray(R.array.food_new_cat_prob);
+ final float new_cat_prob = (float)((food < probs.length) ? probs[food] : 50) / 100f;
+
+ if (cats.size() == 0 || rng.nextFloat() <= new_cat_prob) {
+ cat = Cat.create(this);
+ prefs.addCat(cat);
+ Log.v(TAG, "A new cat is here: " + cat.getName());
+ } else {
+ cat = cats.get(rng.nextInt(cats.size()));
+ Log.v(TAG, "A cat has returned: " + cat.getName());
+ }
+
+ final Notification.Builder builder = cat.buildNotification(this);
+ noman.notify(CAT_NOTIFICATION, builder.build());
+ }
+ }
+ cancelJob(this);
+ return false;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters jobParameters) {
+ return false;
+ }
+
+ public static void registerJob(Context context, long intervalMinutes) {
+ JobScheduler jss = context.getSystemService(JobScheduler.class);
+ jss.cancel(JOB_ID);
+ long interval = intervalMinutes * MINUTES;
+ long jitter = (long)(INTERVAL_JITTER_FRAC * interval);
+ interval += (long)(Math.random() * (2 * jitter)) - jitter;
+ final JobInfo jobInfo = new JobInfo.Builder(JOB_ID,
+ new ComponentName(context, NekoService.class))
+ .setPeriodic(interval, INTERVAL_FLEX)
+ .build();
+
+ Log.v(TAG, "A cat will visit in " + interval + "ms: " + String.valueOf(jobInfo));
+ jss.schedule(jobInfo);
+
+ if (NekoLand.DEBUG_NOTIFICATIONS) {
+ NotificationManager noman = context.getSystemService(NotificationManager.class);
+ noman.notify(500, new Notification.Builder(context)
+ .setSmallIcon(R.drawable.stat_icon)
+ .setContentTitle(String.format("Job scheduled in %d min", (interval / MINUTES)))
+ .setContentText(String.valueOf(jobInfo))
+ .setPriority(Notification.PRIORITY_MIN)
+ .setCategory(Notification.CATEGORY_SERVICE)
+ .setShowWhen(true)
+ .build());
+ }
+ }
+
+ public static void cancelJob(Context context) {
+ JobScheduler jss = context.getSystemService(JobScheduler.class);
+ Log.v(TAG, "Canceling job");
+ jss.cancel(JOB_ID);
+ }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/neko/NekoTile.java b/packages/EasterEgg/src/com/android/egg/neko/NekoTile.java
new file mode 100644
index 0000000000000..d5e143cade0f0
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/neko/NekoTile.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 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.egg.neko;
+
+import android.content.Intent;
+import android.service.quicksettings.Tile;
+import android.service.quicksettings.TileService;
+import android.util.Log;
+
+import com.android.egg.neko.PrefState.PrefsListener;
+
+public class NekoTile extends TileService implements PrefsListener {
+
+ private static final String TAG = "NekoTile";
+
+ private PrefState mPrefs;
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mPrefs = new PrefState(this);
+ }
+
+ @Override
+ public void onStartListening() {
+ super.onStartListening();
+ mPrefs.setListener(this);
+ updateState();
+ }
+
+ @Override
+ public void onStopListening() {
+ super.onStopListening();
+ mPrefs.setListener(null);
+ }
+
+ @Override
+ public void onPrefsChanged() {
+ updateState();
+ }
+
+ private void updateState() {
+ Tile tile = getQsTile();
+ int foodState = mPrefs.getFoodState();
+ Food food = new Food(foodState);
+ tile.setIcon(food.getIcon(this));
+ tile.setLabel(food.getName(this));
+ tile.setState(foodState != 0 ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE);
+ tile.updateTile();
+ }
+
+ @Override
+ public void onClick() {
+ if (mPrefs.getFoodState() != 0) {
+ // there's already food loaded, let's empty it
+ mPrefs.setFoodState(0);
+ NekoService.cancelJob(this);
+ } else {
+ // time to feed the cats
+ if (isLocked()) {
+ if (isSecure()) {
+ Log.d(TAG, "startActivityAndCollapse");
+ Intent intent = new Intent(this, NekoLockedActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivityAndCollapse(intent);
+ } else {
+ unlockAndRun(new Runnable() {
+ @Override
+ public void run() {
+ showNekoDialog();
+ }
+ });
+ }
+ } else {
+ showNekoDialog();
+ }
+ }
+ }
+
+ private void showNekoDialog() {
+ Log.d(TAG, "showNekoDialog");
+ showDialog(new NekoDialog(this));
+ }
+}
diff --git a/packages/EasterEgg/src/com/android/egg/neko/PrefState.java b/packages/EasterEgg/src/com/android/egg/neko/PrefState.java
new file mode 100644
index 0000000000000..5f54180bc2e09
--- /dev/null
+++ b/packages/EasterEgg/src/com/android/egg/neko/PrefState.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2016 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.egg.neko;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+public class PrefState implements OnSharedPreferenceChangeListener {
+
+ private static final String FILE_NAME = "mPrefs";
+
+ private static final String FOOD_STATE = "food";
+
+ private static final String CAT_KEY_PREFIX = "cat:";
+
+ private final Context mContext;
+ private final SharedPreferences mPrefs;
+ private PrefsListener mListener;
+
+ public PrefState(Context context) {
+ mContext = context;
+ mPrefs = mContext.getSharedPreferences(FILE_NAME, 0);
+ }
+
+ // Can also be used for renaming.
+ public void addCat(Cat cat) {
+ mPrefs.edit()
+ .putString(CAT_KEY_PREFIX + String.valueOf(cat.getSeed()), cat.getName())
+ .commit();
+ }
+
+ public void removeCat(Cat cat) {
+ mPrefs.edit()
+ .remove(CAT_KEY_PREFIX + String.valueOf(cat.getSeed()))
+ .commit();
+ }
+
+ public List getCats() {
+ ArrayList cats = new ArrayList<>();
+ Map map = mPrefs.getAll();
+ for (String key : map.keySet()) {
+ if (key.startsWith(CAT_KEY_PREFIX)) {
+ long seed = Long.parseLong(key.substring(CAT_KEY_PREFIX.length()));
+ Cat cat = new Cat(mContext, seed);
+ cat.setName(String.valueOf(map.get(key)));
+ cats.add(cat);
+ }
+ }
+ return cats;
+ }
+
+ public int getFoodState() {
+ return mPrefs.getInt(FOOD_STATE, 0);
+ }
+
+ public void setFoodState(int foodState) {
+ mPrefs.edit().putInt(FOOD_STATE, foodState).commit();
+ }
+
+ public void setListener(PrefsListener listener) {
+ mListener = listener;
+ if (mListener != null) {
+ mPrefs.registerOnSharedPreferenceChangeListener(this);
+ } else {
+ mPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ }
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ mListener.onPrefsChanged();
+ }
+
+ public interface PrefsListener {
+ void onPrefsChanged();
+ }
+}
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 77c43caf314a3..36fbb6a4d49ba 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -442,7 +442,6 @@
-