DessertCaseView.java revision f9f63355e3f05186a3652c0ef3f0039ed97db918
1/*
2 * Copyright (C) 2013 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui;
18
19import android.animation.Animator;
20import android.animation.AnimatorSet;
21import android.animation.ObjectAnimator;
22import android.content.Context;
23import android.content.res.Resources;
24import android.graphics.*;
25import android.graphics.drawable.BitmapDrawable;
26import android.graphics.drawable.Drawable;
27import android.os.Handler;
28import android.util.AttributeSet;
29import android.util.Log;
30import android.util.SparseArray;
31import android.view.View;
32import android.view.animation.AccelerateInterpolator;
33import android.view.animation.AnticipateOvershootInterpolator;
34import android.view.animation.DecelerateInterpolator;
35import android.widget.FrameLayout;
36import android.widget.ImageView;
37
38import java.util.HashSet;
39import java.util.Set;
40
41public class DessertCaseView extends FrameLayout {
42    private static final String TAG = DessertCaseView.class.getSimpleName();
43
44    private static final boolean DEBUG = false;
45
46    static final int START_DELAY = 5000;
47    static final int DELAY = 2000;
48    static final int DURATION = 500;
49
50    private static final int TAG_POS = 0x2000001;
51    private static final int TAG_SPAN = 0x2000002;
52
53    private static final int[] PASTRIES = {
54            R.drawable.dessert_kitkat,      // used with permission
55            R.drawable.dessert_android,     // thx irina
56    };
57
58    private static final int[] RARE_PASTRIES = {
59            R.drawable.dessert_cupcake,     // 2009
60            R.drawable.dessert_donut,       // 2009
61            R.drawable.dessert_eclair,      // 2009
62            R.drawable.dessert_froyo,       // 2010
63            R.drawable.dessert_gingerbread, // 2010
64            R.drawable.dessert_honeycomb,   // 2011
65            R.drawable.dessert_ics,         // 2011
66            R.drawable.dessert_jellybean,   // 2012
67    };
68
69    private static final int[] XRARE_PASTRIES = {
70            R.drawable.dessert_petitfour,   // the original and still delicious
71
72            R.drawable.dessert_donutburger, // remember kids, this was long before cronuts
73
74            R.drawable.dessert_flan,        //     sholes final approach
75                                            //     landing gear punted to flan
76                                            //     runway foam glistens
77                                            //         -- mcleron
78
79            R.drawable.dessert_keylimepie,  // from an alternative timeline
80    };
81    private static final int[] XXRARE_PASTRIES = {
82            R.drawable.dessert_zombiegingerbread, // thx hackbod
83            R.drawable.dessert_dandroid,    // thx morrildl
84            R.drawable.dessert_jandycane,   // thx nes
85    };
86
87    private static final int NUM_PASTRIES = PASTRIES.length + RARE_PASTRIES.length
88            + XRARE_PASTRIES.length + XXRARE_PASTRIES.length;
89
90    private SparseArray<Drawable> mDrawables = new SparseArray<Drawable>(NUM_PASTRIES);
91
92    private static final float[] MASK = {
93            0f,  0f,  0f,  0f, 255f,
94            0f,  0f,  0f,  0f, 255f,
95            0f,  0f,  0f,  0f, 255f,
96            1f,  0f,  0f,  0f, 0f
97    };
98
99    private static final float[] ALPHA_MASK = {
100            0f,  0f,  0f,  0f, 255f,
101            0f,  0f,  0f,  0f, 255f,
102            0f,  0f,  0f,  0f, 255f,
103            0f,  0f,  0f,  1f, 0f
104    };
105
106    private static final float[] WHITE_MASK = {
107            0f,  0f,  0f,  0f, 255f,
108            0f,  0f,  0f,  0f, 255f,
109            0f,  0f,  0f,  0f, 255f,
110            -1f,  0f,  0f,  0f, 255f
111    };
112
113    public static final float SCALE = 0.25f; // natural display size will be SCALE*mCellSize
114
115    private static final float PROB_2X = 0.33f;
116    private static final float PROB_3X = 0.1f;
117    private static final float PROB_4X = 0.01f;
118
119    private boolean mStarted;
120
121    private int mCellSize;
122    private int mWidth, mHeight;
123    private int mRows, mColumns;
124    private View[] mCells;
125
126    private final Set<Point> mFreeList = new HashSet<Point>();
127
128    private final Handler mHandler = new Handler();
129
130    private final Runnable mJuggle = new Runnable() {
131        @Override
132        public void run() {
133            final int N = getChildCount();
134
135            final int K = 1; //irand(1,3);
136            for (int i=0; i<K; i++) {
137                final View child = getChildAt((int) (Math.random() * N));
138                place(child, true);
139            }
140
141            fillFreeList();
142
143            if (mStarted) {
144                mHandler.postDelayed(mJuggle, DELAY);
145            }
146        }
147    };
148
149    public DessertCaseView(Context context) {
150        this(context, null);
151    }
152
153    public DessertCaseView(Context context, AttributeSet attrs) {
154        this(context, attrs, 0);
155    }
156
157    public DessertCaseView(Context context, AttributeSet attrs, int defStyle) {
158        super(context, attrs, defStyle);
159
160        final Resources res = getResources();
161
162        mStarted = false;
163
164        mCellSize = res.getDimensionPixelSize(R.dimen.dessert_case_cell_size);
165        final BitmapFactory.Options opts = new BitmapFactory.Options();
166        if (mCellSize < 512) { // assuming 512x512 images
167            opts.inSampleSize = 2;
168        }
169        for (int[] list : new int[][] { PASTRIES, RARE_PASTRIES, XRARE_PASTRIES, XXRARE_PASTRIES }) {
170            for (int resid : list) {
171                final BitmapDrawable d = new BitmapDrawable(res,
172                        convertToAlphaMask(BitmapFactory.decodeResource(res, resid, opts)));
173                d.setColorFilter(new ColorMatrixColorFilter(ALPHA_MASK));
174                d.setBounds(0, 0, mCellSize, mCellSize);
175                mDrawables.append(resid, d);
176            }
177        }
178        if (DEBUG) setWillNotDraw(false);
179    }
180
181    private static Bitmap convertToAlphaMask(Bitmap b) {
182        Bitmap a = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ALPHA_8);
183        Canvas c = new Canvas(a);
184        Paint pt = new Paint();
185        pt.setColorFilter(new ColorMatrixColorFilter(MASK));
186        c.drawBitmap(b, 0.0f, 0.0f, pt);
187        return a;
188    }
189
190    public void start() {
191        if (!mStarted) {
192            mStarted = true;
193            fillFreeList(DURATION * 4);
194        }
195        mHandler.postDelayed(mJuggle, START_DELAY);
196    }
197
198    public void stop() {
199        mStarted = false;
200        mHandler.removeCallbacks(mJuggle);
201    }
202
203    int pick(int[] a) {
204        return a[(int)(Math.random()*a.length)];
205    }
206
207    <T> T pick(T[] a) {
208        return a[(int)(Math.random()*a.length)];
209    }
210
211    <T> T pick(SparseArray<T> sa) {
212        return sa.valueAt((int)(Math.random()*sa.size()));
213    }
214
215    float[] hsv = new float[] { 0, 1f, .85f };
216    int random_color() {
217//        return 0xFF000000 | (int) (Math.random() * (float) 0xFFFFFF); // totally random
218        final int COLORS = 12;
219        hsv[0] = irand(0,COLORS) * (360f/COLORS);
220        return Color.HSVToColor(hsv);
221    }
222
223    @Override
224    protected synchronized void onSizeChanged (int w, int h, int oldw, int oldh) {
225        super.onSizeChanged(w, h, oldw, oldh);
226        if (mWidth == w && mHeight == h) return;
227
228        final boolean wasStarted = mStarted;
229        if (wasStarted) {
230            stop();
231        }
232
233        mWidth = w;
234        mHeight = h;
235
236        mCells = null;
237        removeAllViewsInLayout();
238        mFreeList.clear();
239
240        mRows = mHeight / mCellSize;
241        mColumns = mWidth / mCellSize;
242
243        mCells = new View[mRows * mColumns];
244
245        if (DEBUG) Log.v(TAG, String.format("New dimensions: %dx%d", mColumns, mRows));
246
247        setScaleX(SCALE);
248        setScaleY(SCALE);
249        setTranslationX(0.5f * (mWidth - mCellSize * mColumns) * SCALE);
250        setTranslationY(0.5f * (mHeight - mCellSize * mRows) * SCALE);
251
252        for (int j=0; j<mRows; j++) {
253            for (int i=0; i<mColumns; i++) {
254                mFreeList.add(new Point(i,j));
255            }
256        }
257
258        if (wasStarted) {
259            start();
260        }
261    }
262
263    public void fillFreeList() {
264        fillFreeList(DURATION);
265    }
266
267    public synchronized void fillFreeList(int animationLen) {
268        final Context ctx = getContext();
269        final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(mCellSize, mCellSize);
270
271        while (! mFreeList.isEmpty()) {
272            Point pt = mFreeList.iterator().next();
273            mFreeList.remove(pt);
274            final int i=pt.x;
275            final int j=pt.y;
276
277            if (mCells[j*mColumns+i] != null) continue;
278            final ImageView v = new ImageView(ctx);
279            v.setOnClickListener(new OnClickListener() {
280                @Override
281                public void onClick(View view) {
282                    place(v, true);
283                    postDelayed(new Runnable() { public void run() { fillFreeList(); } }, DURATION/2);
284                }
285            });
286
287            final int c = random_color();
288            v.setBackgroundColor(c);
289
290            final float which = frand();
291            final Drawable d;
292            if (which < 0.0005f) {
293                d = mDrawables.get(pick(XXRARE_PASTRIES));
294            } else if (which < 0.005f) {
295                d = mDrawables.get(pick(XRARE_PASTRIES));
296            } else if (which < 0.5f) {
297                d = mDrawables.get(pick(RARE_PASTRIES));
298            } else if (which < 0.7f) {
299                d = mDrawables.get(pick(PASTRIES));
300            } else {
301                d = null;
302            }
303            if (d != null) {
304                v.getOverlay().add(d);
305            }
306
307            v.setLayerType(View.LAYER_TYPE_HARDWARE, null);
308
309            lp.width = lp.height = mCellSize;
310            addView(v, lp);
311            place(v, pt, false);
312            if (animationLen > 0) {
313                final float s = (Integer) v.getTag(TAG_SPAN);
314                v.setScaleX(0.5f * s);
315                v.setScaleY(0.5f * s);
316                v.setAlpha(0f);
317                v.animate().scaleX(s).scaleY(s).alpha(1f).setDuration(animationLen);
318            }
319        }
320    }
321
322    public void place(View v, boolean animate) {
323        place(v, new Point(irand(0, mColumns), irand(0, mRows)), animate);
324    }
325
326    private final HashSet<View> tmpSet = new HashSet<View>();
327    public synchronized void place(View v, Point pt, boolean animate) {
328        final int i = pt.x;
329        final int j = pt.y;
330        final float rnd = frand();
331        if (v.getTag(TAG_POS) != null) {
332            for (final Point oc : getOccupied(v)) {
333                mFreeList.add(oc);
334                mCells[oc.y*mColumns + oc.x] = null;
335            }
336        }
337        int scale = 1;
338        if (rnd < PROB_4X) {
339            if (!(i >= mColumns-3 || j >= mRows-3)) {
340                scale = 4;
341            }
342        } else if (rnd < PROB_3X) {
343            if (!(i >= mColumns-2 || j >= mRows-2)) {
344                scale = 3;
345            }
346        } else if (rnd < PROB_2X) {
347            if (!(i == mColumns-1 || j == mRows-1)) {
348                scale = 2;
349            }
350        }
351
352        v.setTag(TAG_POS, pt);
353        v.setTag(TAG_SPAN, scale);
354
355        tmpSet.clear();
356
357        final Point[] occupied = getOccupied(v);
358        for (final Point oc : occupied) {
359            final View squatter = mCells[oc.y*mColumns + oc.x];
360            if (squatter != null) {
361                tmpSet.add(squatter);
362            }
363        }
364
365        for (final View squatter : tmpSet) {
366            for (final Point sq : getOccupied(squatter)) {
367                mFreeList.add(sq);
368                mCells[sq.y*mColumns + sq.x] = null;
369            }
370            if (squatter != v) {
371                squatter.setTag(TAG_POS, null);
372                if (animate) {
373                    squatter.animate().scaleX(0.5f).scaleY(0.5f).alpha(0)
374                            .setDuration(DURATION)
375                            .setInterpolator(new AccelerateInterpolator())
376                            .setListener(new Animator.AnimatorListener() {
377                                public void onAnimationStart(Animator animator) { }
378                                public void onAnimationEnd(Animator animator) {
379                                    removeView(squatter);
380                                }
381                                public void onAnimationCancel(Animator animator) { }
382                                public void onAnimationRepeat(Animator animator) { }
383                            })
384                            .start();
385                } else {
386                    removeView(squatter);
387                }
388            }
389        }
390
391        for (final Point oc : occupied) {
392            mCells[oc.y*mColumns + oc.x] = v;
393            mFreeList.remove(oc);
394        }
395
396        final float rot = (float)irand(0, 4) * 90f;
397
398        if (animate) {
399            v.bringToFront();
400            AnimatorSet set1 = new AnimatorSet();
401            set1.playTogether(
402                    ObjectAnimator.ofFloat(v, View.SCALE_X, (float) scale),
403                    ObjectAnimator.ofFloat(v, View.SCALE_Y, (float) scale)
404            );
405            set1.setInterpolator(new AnticipateOvershootInterpolator());
406            set1.setDuration(DURATION);
407            set1.start();
408
409            AnimatorSet set2 = new AnimatorSet();
410            set2.playTogether(
411                    ObjectAnimator.ofFloat(v, View.ROTATION, rot),
412                    ObjectAnimator.ofFloat(v, View.X, i* mCellSize + (scale-1) * mCellSize /2),
413                    ObjectAnimator.ofFloat(v, View.Y, j* mCellSize + (scale-1) * mCellSize /2)
414            );
415            set2.setInterpolator(new DecelerateInterpolator());
416            set2.setDuration(DURATION);
417            set2.start();
418        } else {
419            v.setX(i * mCellSize + (scale-1) * mCellSize /2);
420            v.setY(j * mCellSize + (scale-1) * mCellSize /2);
421            v.setScaleX((float) scale);
422            v.setScaleY((float) scale);
423            v.setRotation(rot);
424        }
425    }
426
427    private Point[] getOccupied(View v) {
428        final int scale = (Integer) v.getTag(TAG_SPAN);
429        final Point pt = (Point)v.getTag(TAG_POS);
430        if (pt == null || scale == 0) return new Point[0];
431
432        final Point[] result = new Point[scale * scale];
433        int p=0;
434        for (int i=0; i<scale; i++) {
435            for (int j=0; j<scale; j++) {
436                result[p++] = new Point(pt.x + i, pt.y + j);
437            }
438        }
439        return result;
440    }
441
442    static float frand() {
443        return (float)(Math.random());
444    }
445
446    static float frand(float a, float b) {
447        return (frand() * (b-a) + a);
448    }
449
450    static int irand(int a, int b) {
451        return (int)(frand(a, b));
452    }
453
454    @Override
455    public void onDraw(Canvas c) {
456        super.onDraw(c);
457        if (!DEBUG) return;
458
459        Paint pt = new Paint();
460        pt.setStyle(Paint.Style.STROKE);
461        pt.setColor(0xFFCCCCCC);
462        pt.setStrokeWidth(2.0f);
463
464        final Rect check = new Rect();
465        final int N = getChildCount();
466        for (int i = 0; i < N; i++) {
467            View stone = getChildAt(i);
468
469            stone.getHitRect(check);
470
471            c.drawRect(check, pt);
472        }
473    }
474
475    public static class RescalingContainer extends FrameLayout {
476        private static final int SYSTEM_UI_MODE_800 = 0x00000800;
477        private DessertCaseView mView;
478        private float mDarkness;
479
480        public RescalingContainer(Context context) {
481            super(context);
482
483            setSystemUiVisibility(0
484                    | View.SYSTEM_UI_FLAG_FULLSCREEN
485                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
486                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
487                    | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
488                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
489                    | SYSTEM_UI_MODE_800
490            );
491        }
492
493        public void setView(DessertCaseView v) {
494            addView(v);
495            mView = v;
496        }
497
498        @Override
499        protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
500            final float w = right-left;
501            final float h = bottom-top;
502            final int w2 = (int) (w / mView.SCALE / 2);
503            final int h2 = (int) (h / mView.SCALE / 2);
504            final int cx = (int) (left + w * 0.5f);
505            final int cy = (int) (top + h * 0.5f);
506            mView.layout(cx - w2, cy - h2, cx + w2, cy + h2);
507        }
508
509        public void setDarkness(float p) {
510            mDarkness = p;
511            getDarkness();
512            final int x = (int) (p * 0xff);
513            setBackgroundColor(x << 24 & 0xFF000000);
514        }
515
516        public float getDarkness() {
517            return mDarkness;
518        }
519    }
520}
521