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