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