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