• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
notSoRandom(long seed)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 
frandrange(Random r, float a, float b)53     public static final float frandrange(Random r, float a, float b) {
54         return (b-a)*r.nextFloat() + a;
55     }
56 
choose(Random r, Object...l)57     public static final Object choose(Random r, Object...l) {
58         return l[r.nextInt(l.length)];
59     }
60 
chooseP(Random r, int[] a)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 
getColorIndex(int q, int[] a)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 
tint(int color, Drawable ... ds)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 
isDark(int color)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 
Cat(Context context, long seed)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