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