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