Home | History | Annotate | Download | only in neko
      1 /*
      2  * Copyright (C) 2016 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
      5  * except in compliance with the License. You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     11  * KIND, either express or implied. See the License for the specific language governing
     12  * permissions and limitations under the License.
     13  */
     14 
     15 package com.android.egg.neko;
     16 
     17 import android.app.Notification;
     18 import android.app.PendingIntent;
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.res.Resources;
     22 import android.graphics.*;
     23 import android.graphics.drawable.Drawable;
     24 import android.graphics.drawable.Icon;
     25 import android.os.Bundle;
     26 
     27 import java.io.ByteArrayOutputStream;
     28 import java.util.Random;
     29 import java.util.concurrent.ThreadLocalRandom;
     30 
     31 import com.android.egg.R;
     32 import com.android.internal.logging.MetricsLogger;
     33 
     34 public class Cat extends Drawable {
     35     public static final long[] PURR = {0, 40, 20, 40, 20, 40, 20, 40, 20, 40, 20, 40};
     36 
     37     private Random mNotSoRandom;
     38     private Bitmap mBitmap;
     39     private long mSeed;
     40     private String mName;
     41     private int mBodyColor;
     42     private int mFootType;
     43     private boolean mBowTie;
     44 
     45     private synchronized Random notSoRandom(long seed) {
     46         if (mNotSoRandom == null) {
     47             mNotSoRandom = new Random();
     48             mNotSoRandom.setSeed(seed);
     49         }
     50         return mNotSoRandom;
     51     }
     52 
     53     public static final float frandrange(Random r, float a, float b) {
     54         return (b-a)*r.nextFloat() + a;
     55     }
     56 
     57     public static final Object choose(Random r, Object...l) {
     58         return l[r.nextInt(l.length)];
     59     }
     60 
     61     public static final int chooseP(Random r, int[] a) {
     62         int pct = r.nextInt(1000);
     63         final int stop = a.length-2;
     64         int i=0;
     65         while (i<stop) {
     66             pct -= a[i];
     67             if (pct < 0) break;
     68             i+=2;
     69         }
     70         return a[i+1];
     71     }
     72 
     73     public static final int getColorIndex(int q, int[] a) {
     74         for(int i = 1; i < a.length; i+=2) {
     75             if (a[i] == q) {
     76                 return i/2;
     77             }
     78         }
     79         return -1;
     80     }
     81 
     82     public static final int[] P_BODY_COLORS = {
     83             180, 0xFF212121, // black
     84             180, 0xFFFFFFFF, // white
     85             140, 0xFF616161, // gray
     86             140, 0xFF795548, // brown
     87             100, 0xFF90A4AE, // steel
     88             100, 0xFFFFF9C4, // buff
     89             100, 0xFFFF8F00, // orange
     90               5, 0xFF29B6F6, // blue..?
     91               5, 0xFFFFCDD2, // pink!?
     92               5, 0xFFCE93D8, // purple?!?!?
     93               4, 0xFF43A047, // yeah, why not green
     94               1, 0,          // ?!?!?!
     95     };
     96 
     97     public static final int[] P_COLLAR_COLORS = {
     98             250, 0xFFFFFFFF,
     99             250, 0xFF000000,
    100             250, 0xFFF44336,
    101              50, 0xFF1976D2,
    102              50, 0xFFFDD835,
    103              50, 0xFFFB8C00,
    104              50, 0xFFF48FB1,
    105              50, 0xFF4CAF50,
    106     };
    107 
    108     public static final int[] P_BELLY_COLORS = {
    109             750, 0,
    110             250, 0xFFFFFFFF,
    111     };
    112 
    113     public static final int[] P_DARK_SPOT_COLORS = {
    114             700, 0,
    115             250, 0xFF212121,
    116              50, 0xFF6D4C41,
    117     };
    118 
    119     public static final int[] P_LIGHT_SPOT_COLORS = {
    120             700, 0,
    121             300, 0xFFFFFFFF,
    122     };
    123 
    124     private CatParts D;
    125 
    126     public static void tint(int color, Drawable ... ds) {
    127         for (Drawable d : ds) {
    128             if (d != null) {
    129                 d.mutate().setTint(color);
    130             }
    131         }
    132     }
    133 
    134     public static boolean isDark(int color) {
    135         final int r = (color & 0xFF0000) >> 16;
    136         final int g = (color & 0x00FF00) >> 8;
    137         final int b = color & 0x0000FF;
    138         return (r + g + b) < 0x80;
    139     }
    140 
    141     public Cat(Context context, long seed) {
    142         D = new CatParts(context);
    143         mSeed = seed;
    144 
    145         setName(context.getString(R.string.default_cat_name,
    146                 String.valueOf(mSeed % 1000)));
    147 
    148         final Random nsr = notSoRandom(seed);
    149 
    150         // body color
    151         mBodyColor = chooseP(nsr, P_BODY_COLORS);
    152         if (mBodyColor == 0) mBodyColor = Color.HSVToColor(new float[] {
    153                 nsr.nextFloat()*360f, frandrange(nsr,0.5f,1f), frandrange(nsr,0.5f, 1f)});
    154 
    155         tint(mBodyColor, D.body, D.head, D.leg1, D.leg2, D.leg3, D.leg4, D.tail,
    156                 D.leftEar, D.rightEar, D.foot1, D.foot2, D.foot3, D.foot4, D.tailCap);
    157         tint(0x20000000, D.leg2Shadow, D.tailShadow);
    158         if (isDark(mBodyColor)) {
    159             tint(0xFFFFFFFF, D.leftEye, D.rightEye, D.mouth, D.nose);
    160         }
    161         tint(isDark(mBodyColor) ? 0xFFEF9A9A : 0x20D50000, D.leftEarInside, D.rightEarInside);
    162 
    163         tint(chooseP(nsr, P_BELLY_COLORS), D.belly);
    164         tint(chooseP(nsr, P_BELLY_COLORS), D.back);
    165         final int faceColor = chooseP(nsr, P_BELLY_COLORS);
    166         tint(faceColor, D.faceSpot);
    167         if (!isDark(faceColor)) {
    168             tint(0xFF000000, D.mouth, D.nose);
    169         }
    170 
    171         mFootType = 0;
    172         if (nsr.nextFloat() < 0.25f) {
    173             mFootType = 4;
    174             tint(0xFFFFFFFF, D.foot1, D.foot2, D.foot3, D.foot4);
    175         } else {
    176             if (nsr.nextFloat() < 0.25f) {
    177                 mFootType = 2;
    178                 tint(0xFFFFFFFF, D.foot1, D.foot3);
    179             } else if (nsr.nextFloat() < 0.25f) {
    180                 mFootType = 3; // maybe -2 would be better? meh.
    181                 tint(0xFFFFFFFF, D.foot2, D.foot4);
    182             } else if (nsr.nextFloat() < 0.1f) {
    183                 mFootType = 1;
    184                 tint(0xFFFFFFFF, (Drawable) choose(nsr, D.foot1, D.foot2, D.foot3, D.foot4));
    185             }
    186         }
    187 
    188         tint(nsr.nextFloat() < 0.333f ? 0xFFFFFFFF : mBodyColor, D.tailCap);
    189 
    190         final int capColor = chooseP(nsr, isDark(mBodyColor) ? P_LIGHT_SPOT_COLORS : P_DARK_SPOT_COLORS);
    191         tint(capColor, D.cap);
    192         //tint(chooseP(nsr, isDark(bodyColor) ? P_LIGHT_SPOT_COLORS : P_DARK_SPOT_COLORS), D.nose);
    193 
    194         final int collarColor = chooseP(nsr, P_COLLAR_COLORS);
    195         tint(collarColor, D.collar);
    196         mBowTie = nsr.nextFloat() < 0.1f;
    197         tint(mBowTie ? collarColor : 0, D.bowtie);
    198     }
    199 
    200     public static Cat create(Context context) {
    201         return new Cat(context, Math.abs(ThreadLocalRandom.current().nextInt()));
    202     }
    203 
    204     public Notification.Builder buildNotification(Context context) {
    205         final Bundle extras = new Bundle();
    206         extras.putString("android.substName", context.getString(R.string.notification_name));
    207         final Intent intent = new Intent(Intent.ACTION_MAIN)
    208                 .setClass(context, NekoLand.class)
    209                 .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    210         return new Notification.Builder(context)
    211                 .setSmallIcon(Icon.createWithResource(context, R.drawable.stat_icon))
    212                 .setLargeIcon(createNotificationLargeIcon(context))
    213                 .setColor(getBodyColor())
    214                 .setPriority(Notification.PRIORITY_LOW)
    215                 .setContentTitle(context.getString(R.string.notification_title))
    216                 .setShowWhen(true)
    217                 .setCategory(Notification.CATEGORY_STATUS)
    218                 .setContentText(getName())
    219                 .setContentIntent(PendingIntent.getActivity(context, 0, intent, 0))
    220                 .setAutoCancel(true)
    221                 .setVibrate(PURR)
    222                 .addExtras(extras);
    223     }
    224 
    225     public long getSeed() {
    226         return mSeed;
    227     }
    228 
    229     @Override
    230     public void draw(Canvas canvas) {
    231         final int w = Math.min(canvas.getWidth(), canvas.getHeight());
    232         final int h = w;
    233 
    234         if (mBitmap == null || mBitmap.getWidth() != w || mBitmap.getHeight() != h) {
    235             mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    236             final Canvas bitCanvas = new Canvas(mBitmap);
    237             slowDraw(bitCanvas, 0, 0, w, h);
    238         }
    239         canvas.drawBitmap(mBitmap, 0, 0, null);
    240     }
    241 
    242     private void slowDraw(Canvas canvas, int x, int y, int w, int h) {
    243         for (int i = 0; i < D.drawingOrder.length; i++) {
    244             final Drawable d = D.drawingOrder[i];
    245             if (d != null) {
    246                 d.setBounds(x, y, x+w, y+h);
    247                 d.draw(canvas);
    248             }
    249         }
    250 
    251     }
    252 
    253     public Bitmap createBitmap(int w, int h) {
    254         if (mBitmap != null && mBitmap.getWidth() == w && mBitmap.getHeight() == h) {
    255             return mBitmap.copy(mBitmap.getConfig(), true);
    256         }
    257         Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    258         slowDraw(new Canvas(result), 0, 0, w, h);
    259         return result;
    260     }
    261 
    262     public static Icon recompressIcon(Icon bitmapIcon) {
    263         if (bitmapIcon.getType() != Icon.TYPE_BITMAP) return bitmapIcon;
    264         final Bitmap bits = bitmapIcon.getBitmap();
    265         final ByteArrayOutputStream ostream = new ByteArrayOutputStream(
    266                 bits.getWidth() * bits.getHeight() * 2); // guess 50% compression
    267         final boolean ok = bits.compress(Bitmap.CompressFormat.PNG, 100, ostream);
    268         if (!ok) return null;
    269         return Icon.createWithData(ostream.toByteArray(), 0, ostream.size());
    270     }
    271 
    272     public Icon createNotificationLargeIcon(Context context) {
    273         final Resources res = context.getResources();
    274         final int w = 2*res.getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
    275         final int h = 2*res.getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
    276         return recompressIcon(createIcon(context, w, h));
    277     }
    278 
    279     public Icon createIcon(Context context, int w, int h) {
    280         Bitmap result = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
    281         final Canvas canvas = new Canvas(result);
    282         final Paint pt = new Paint();
    283         float[] hsv = new float[3];
    284         Color.colorToHSV(mBodyColor, hsv);
    285         hsv[2] = (hsv[2]>0.5f)
    286                 ? (hsv[2] - 0.25f)
    287                 : (hsv[2] + 0.25f);
    288         pt.setColor(Color.HSVToColor(hsv));
    289         float r = w/2;
    290         canvas.drawCircle(r, r, r, pt);
    291         int m = w/10;
    292 
    293         slowDraw(canvas, m, m, w-m-m, h-m-m);
    294 
    295         return Icon.createWithBitmap(result);
    296     }
    297 
    298     @Override
    299     public void setAlpha(int i) {
    300 
    301     }
    302 
    303     @Override
    304     public void setColorFilter(ColorFilter colorFilter) {
    305 
    306     }
    307 
    308     @Override
    309     public int getOpacity() {
    310         return PixelFormat.TRANSLUCENT;
    311     }
    312 
    313     public String getName() {
    314         return mName;
    315     }
    316 
    317     public void setName(String name) {
    318         this.mName = name;
    319     }
    320 
    321     public int getBodyColor() {
    322         return mBodyColor;
    323     }
    324 
    325     public void logAdd(Context context) {
    326         logCatAction(context, "egg_neko_add");
    327     }
    328 
    329     public void logRename(Context context) {
    330         logCatAction(context, "egg_neko_rename");
    331     }
    332 
    333     public void logRemove(Context context) {
    334         logCatAction(context, "egg_neko_remove");
    335     }
    336 
    337     public void logShare(Context context) {
    338         logCatAction(context, "egg_neko_share");
    339     }
    340 
    341     private void logCatAction(Context context, String prefix) {
    342         MetricsLogger.count(context, prefix, 1);
    343         MetricsLogger.histogram(context, prefix +"_color",
    344                 getColorIndex(mBodyColor, P_BODY_COLORS));
    345         MetricsLogger.histogram(context, prefix + "_bowtie", mBowTie ? 1 : 0);
    346         MetricsLogger.histogram(context, prefix + "_feet", mFootType);
    347     }
    348 
    349     public static class CatParts {
    350         public Drawable leftEar;
    351         public Drawable rightEar;
    352         public Drawable rightEarInside;
    353         public Drawable leftEarInside;
    354         public Drawable head;
    355         public Drawable faceSpot;
    356         public Drawable cap;
    357         public Drawable mouth;
    358         public Drawable body;
    359         public Drawable foot1;
    360         public Drawable leg1;
    361         public Drawable foot2;
    362         public Drawable leg2;
    363         public Drawable foot3;
    364         public Drawable leg3;
    365         public Drawable foot4;
    366         public Drawable leg4;
    367         public Drawable tail;
    368         public Drawable leg2Shadow;
    369         public Drawable tailShadow;
    370         public Drawable tailCap;
    371         public Drawable belly;
    372         public Drawable back;
    373         public Drawable rightEye;
    374         public Drawable leftEye;
    375         public Drawable nose;
    376         public Drawable bowtie;
    377         public Drawable collar;
    378         public Drawable[] drawingOrder;
    379 
    380         public CatParts(Context context) {
    381             body = context.getDrawable(R.drawable.body);
    382             head = context.getDrawable(R.drawable.head);
    383             leg1 = context.getDrawable(R.drawable.leg1);
    384             leg2 = context.getDrawable(R.drawable.leg2);
    385             leg3 = context.getDrawable(R.drawable.leg3);
    386             leg4 = context.getDrawable(R.drawable.leg4);
    387             tail = context.getDrawable(R.drawable.tail);
    388             leftEar = context.getDrawable(R.drawable.left_ear);
    389             rightEar = context.getDrawable(R.drawable.right_ear);
    390             rightEarInside = context.getDrawable(R.drawable.right_ear_inside);
    391             leftEarInside = context.getDrawable(R.drawable.left_ear_inside);
    392             faceSpot = context.getDrawable(R.drawable.face_spot);
    393             cap = context.getDrawable(R.drawable.cap);
    394             mouth = context.getDrawable(R.drawable.mouth);
    395             foot4 = context.getDrawable(R.drawable.foot4);
    396             foot3 = context.getDrawable(R.drawable.foot3);
    397             foot1 = context.getDrawable(R.drawable.foot1);
    398             foot2 = context.getDrawable(R.drawable.foot2);
    399             leg2Shadow = context.getDrawable(R.drawable.leg2_shadow);
    400             tailShadow = context.getDrawable(R.drawable.tail_shadow);
    401             tailCap = context.getDrawable(R.drawable.tail_cap);
    402             belly = context.getDrawable(R.drawable.belly);
    403             back = context.getDrawable(R.drawable.back);
    404             rightEye = context.getDrawable(R.drawable.right_eye);
    405             leftEye = context.getDrawable(R.drawable.left_eye);
    406             nose = context.getDrawable(R.drawable.nose);
    407             collar = context.getDrawable(R.drawable.collar);
    408             bowtie = context.getDrawable(R.drawable.bowtie);
    409             drawingOrder = getDrawingOrder();
    410         }
    411         private Drawable[] getDrawingOrder() {
    412             return new Drawable[] {
    413                     collar,
    414                     leftEar, leftEarInside, rightEar, rightEarInside,
    415                     head,
    416                     faceSpot,
    417                     cap,
    418                     leftEye, rightEye,
    419                     nose, mouth,
    420                     tail, tailCap, tailShadow,
    421                     foot1, leg1,
    422                     foot2, leg2,
    423                     foot3, leg3,
    424                     foot4, leg4,
    425                     leg2Shadow,
    426                     body, belly,
    427                     bowtie
    428             };
    429         }
    430     }
    431 }
    432