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 java.util.HashSet; 47 import java.util.Set; 48 49 public 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 107 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 114 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 DessertCaseView(Context context)157 public DessertCaseView(Context context) { 158 this(context, null); 159 } 160 DessertCaseView(Context context, AttributeSet attrs)161 public DessertCaseView(Context context, AttributeSet attrs) { 162 this(context, attrs, 0); 163 } 164 DessertCaseView(Context context, AttributeSet attrs, int defStyle)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 } 177 opts.inMutable = true; 178 Bitmap loaded = null; 179 for (int[] list : new int[][] { PASTRIES, RARE_PASTRIES, XRARE_PASTRIES, XXRARE_PASTRIES }) { 180 for (int resid : list) { 181 opts.inBitmap = loaded; 182 loaded = BitmapFactory.decodeResource(res, resid, opts); 183 final BitmapDrawable d = new BitmapDrawable(res, convertToAlphaMask(loaded)); 184 d.setColorFilter(new ColorMatrixColorFilter(ALPHA_MASK)); 185 d.setBounds(0, 0, mCellSize, mCellSize); 186 mDrawables.append(resid, d); 187 } 188 } 189 loaded = null; 190 if (DEBUG) setWillNotDraw(false); 191 } 192 convertToAlphaMask(Bitmap b)193 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 start()202 public void start() { 203 if (!mStarted) { 204 mStarted = true; 205 fillFreeList(DURATION * 4); 206 } 207 mHandler.postDelayed(mJuggle, START_DELAY); 208 } 209 stop()210 public void stop() { 211 mStarted = false; 212 mHandler.removeCallbacks(mJuggle); 213 } 214 pick(int[] a)215 int pick(int[] a) { 216 return a[(int)(Math.random()*a.length)]; 217 } 218 pick(T[] a)219 <T> T pick(T[] a) { 220 return a[(int)(Math.random()*a.length)]; 221 } 222 pick(SparseArray<T> sa)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 }; random_color()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 onSizeChanged(int w, int h, int oldw, int oldh)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 fillFreeList()275 public void fillFreeList() { 276 fillFreeList(DURATION); 277 } 278 fillFreeList(int animationLen)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; 304 if (which < 0.0005f) { 305 d = mDrawables.get(pick(XXRARE_PASTRIES)); 306 } else if (which < 0.005f) { 307 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 319 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); 327 v.animate().withLayer().scaleX(s).scaleY(s).alpha(1f).setDuration(animationLen); 328 } 329 } 330 } 331 place(View v, boolean animate)332 public void place(View v, boolean animate) { 333 place(v, new Point(irand(0, mColumns), irand(0, mRows)), animate); 334 } 335 336 // we don't have .withLayer() on general Animators makeHardwareLayerListener(final View v)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 351 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) { 398 squatter.animate().withLayer() 399 .scaleX(0.5f).scaleY(0.5f).alpha(0) 400 .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(); 426 427 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); 434 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); 443 444 set1.addListener(makeHardwareLayerListener(v)); 445 446 set1.start(); 447 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 { 506 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 517 | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY 518 ); 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 } 549