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