blob: dd1bd07f3298706d7670043eb7683eca42dbf51d [file] [log] [blame]
Dan Sandler27a9fcc2016-06-22 00:05:11 -04001/*
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
15package com.android.egg.neko;
16
17import android.app.Notification;
18import android.app.PendingIntent;
Dan Sandler27a9fcc2016-06-22 00:05:11 -040019import android.content.Context;
20import android.content.Intent;
21import android.content.res.Resources;
22import android.graphics.*;
23import android.graphics.drawable.Drawable;
24import android.graphics.drawable.Icon;
25import android.os.Bundle;
26
Dan Sandler1e529092016-07-16 01:01:19 -040027import java.io.ByteArrayOutputStream;
Dan Sandler27a9fcc2016-06-22 00:05:11 -040028import java.util.Random;
29import java.util.concurrent.ThreadLocalRandom;
30
31import com.android.egg.R;
Chris Wrena801d402016-06-29 11:30:59 -040032import com.android.internal.logging.MetricsLogger;
Dan Sandler27a9fcc2016-06-22 00:05:11 -040033
Dan Sandler7bcd6aa2017-09-04 21:36:01 -040034import static com.android.egg.neko.NekoLand.CHAN_ID;
35
Dan Sandler27a9fcc2016-06-22 00:05:11 -040036public class Cat extends Drawable {
Dan Sandler610f6ed2016-06-28 01:25:08 -040037 public static final long[] PURR = {0, 40, 20, 40, 20, 40, 20, 40, 20, 40, 20, 40};
38
Dan Sandler27a9fcc2016-06-22 00:05:11 -040039 private Random mNotSoRandom;
40 private Bitmap mBitmap;
41 private long mSeed;
42 private String mName;
43 private int mBodyColor;
Chris Wrena801d402016-06-29 11:30:59 -040044 private int mFootType;
45 private boolean mBowTie;
Dan Sandler27a9fcc2016-06-22 00:05:11 -040046
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
55 public static final float frandrange(Random r, float a, float b) {
56 return (b-a)*r.nextFloat() + a;
57 }
58
59 public static final Object choose(Random r, Object...l) {
60 return l[r.nextInt(l.length)];
61 }
62
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
Chris Wrena801d402016-06-29 11:30:59 -040075 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
Dan Sandler27a9fcc2016-06-22 00:05:11 -040084 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
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
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
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,
Dan Sandler7c2e7302016-07-16 01:25:31 -0400148 String.valueOf(mSeed % 1000)));
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400149
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
Chris Wrena801d402016-06-29 11:30:59 -0400173 mFootType = 0;
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400174 if (nsr.nextFloat() < 0.25f) {
Chris Wrena801d402016-06-29 11:30:59 -0400175 mFootType = 4;
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400176 tint(0xFFFFFFFF, D.foot1, D.foot2, D.foot3, D.foot4);
177 } else {
178 if (nsr.nextFloat() < 0.25f) {
Chris Wrena801d402016-06-29 11:30:59 -0400179 mFootType = 2;
Dan Sandler4a1bcd92016-07-16 00:39:21 -0400180 tint(0xFFFFFFFF, D.foot1, D.foot3);
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400181 } else if (nsr.nextFloat() < 0.25f) {
Chris Wrena801d402016-06-29 11:30:59 -0400182 mFootType = 3; // maybe -2 would be better? meh.
Dan Sandler4a1bcd92016-07-16 00:39:21 -0400183 tint(0xFFFFFFFF, D.foot2, D.foot4);
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400184 } else if (nsr.nextFloat() < 0.1f) {
Chris Wrena801d402016-06-29 11:30:59 -0400185 mFootType = 1;
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400186 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);
Chris Wrena801d402016-06-29 11:30:59 -0400198 mBowTie = nsr.nextFloat() < 0.1f;
199 tint(mBowTie ? collarColor : 0, D.bowtie);
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400200 }
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))
Dan Sandler1e529092016-07-16 01:01:19 -0400214 .setLargeIcon(createNotificationLargeIcon(context))
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400215 .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)
Dan Sandler7bcd6aa2017-09-04 21:36:01 -0400223 .setChannel(CHAN_ID)
Dan Sandler610f6ed2016-06-28 01:25:08 -0400224 .setVibrate(PURR)
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400225 .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
Dan Sandler1e529092016-07-16 01:01:19 -0400265 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 }
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400274
Dan Sandler1e529092016-07-16 01:01:19 -0400275 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) {
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400283 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
Chris Wrena801d402016-06-29 11:30:59 -0400328 public void logAdd(Context context) {
329 logCatAction(context, "egg_neko_add");
330 }
331
Chris Wren1bdc7702016-07-01 14:06:40 -0400332 public void logRename(Context context) {
333 logCatAction(context, "egg_neko_rename");
334 }
335
Chris Wrena801d402016-06-29 11:30:59 -0400336 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
Dan Sandler27a9fcc2016-06-22 00:05:11 -0400352 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}