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