/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.*; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.Handler; import android.util.AttributeSet; import android.util.Log; import android.util.SparseArray; import android.view.View; import android.view.animation.AccelerateInterpolator; import android.view.animation.AnticipateOvershootInterpolator; import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.ImageView; import java.util.HashSet; import java.util.Set; public class DessertCaseView extends FrameLayout { private static final String TAG = DessertCaseView.class.getSimpleName(); private static final boolean DEBUG = false; static final int START_DELAY = 5000; static final int DELAY = 2000; static final int DURATION = 500; private static final int TAG_POS = 0x2000001; private static final int TAG_SPAN = 0x2000002; private static final int[] PASTRIES = { R.drawable.dessert_kitkat, // used with permission R.drawable.dessert_android, // thx irina }; private static final int[] RARE_PASTRIES = { R.drawable.dessert_cupcake, // 2009 R.drawable.dessert_donut, // 2009 R.drawable.dessert_eclair, // 2009 R.drawable.dessert_froyo, // 2010 R.drawable.dessert_gingerbread, // 2010 R.drawable.dessert_honeycomb, // 2011 R.drawable.dessert_ics, // 2011 R.drawable.dessert_jellybean, // 2012 }; private static final int[] XRARE_PASTRIES = { R.drawable.dessert_petitfour, // the original and still delicious R.drawable.dessert_donutburger, // remember kids, this was long before cronuts R.drawable.dessert_flan, // sholes final approach // landing gear punted to flan // runway foam glistens // -- mcleron R.drawable.dessert_keylimepie, // from an alternative timeline }; private static final int[] XXRARE_PASTRIES = { R.drawable.dessert_zombiegingerbread, // thx hackbod R.drawable.dessert_dandroid, // thx morrildl R.drawable.dessert_jandycane, // thx nes }; private static final int NUM_PASTRIES = PASTRIES.length + RARE_PASTRIES.length + XRARE_PASTRIES.length + XXRARE_PASTRIES.length; private SparseArray<Drawable> mDrawables = new SparseArray<Drawable>(NUM_PASTRIES); private static final float[] MASK = { 0f, 0f, 0f, 0f, 255f, 0f, 0f, 0f, 0f, 255f, 0f, 0f, 0f, 0f, 255f, 1f, 0f, 0f, 0f, 0f }; private static final float[] ALPHA_MASK = { 0f, 0f, 0f, 0f, 255f, 0f, 0f, 0f, 0f, 255f, 0f, 0f, 0f, 0f, 255f, 0f, 0f, 0f, 1f, 0f }; private static final float[] WHITE_MASK = { 0f, 0f, 0f, 0f, 255f, 0f, 0f, 0f, 0f, 255f, 0f, 0f, 0f, 0f, 255f, -1f, 0f, 0f, 0f, 255f }; public static final float SCALE = 0.25f; // natural display size will be SCALE*mCellSize private static final float PROB_2X = 0.33f; private static final float PROB_3X = 0.1f; private static final float PROB_4X = 0.01f; private boolean mStarted; private int mCellSize; private int mWidth, mHeight; private int mRows, mColumns; private View[] mCells; private final Set<Point> mFreeList = new HashSet<Point>(); private final Handler mHandler = new Handler(); private final Runnable mJuggle = new Runnable() { @Override public void run() { final int N = getChildCount(); final int K = 1; //irand(1,3); for (int i=0; i<K; i++) { final View child = getChildAt((int) (Math.random() * N)); place(child, true); } fillFreeList(); if (mStarted) { mHandler.postDelayed(mJuggle, DELAY); } } }; public DessertCaseView(Context context) { this(context, null); } public DessertCaseView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DessertCaseView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); final Resources res = getResources(); mStarted = false; mCellSize = res.getDimensionPixelSize(R.dimen.dessert_case_cell_size); final BitmapFactory.Options opts = new BitmapFactory.Options(); if (mCellSize < 512) { // assuming 512x512 images opts.inSampleSize = 2; } opts.inMutable = true; Bitmap loaded = null; for (int[] list : new int[][] { PASTRIES, RARE_PASTRIES, XRARE_PASTRIES, XXRARE_PASTRIES }) { for (int resid : list) { opts.inBitmap = loaded; loaded = BitmapFactory.decodeResource(res, resid, opts); final BitmapDrawable d = new BitmapDrawable(res, convertToAlphaMask(loaded)); d.setColorFilter(new ColorMatrixColorFilter(ALPHA_MASK)); d.setBounds(0, 0, mCellSize, mCellSize); mDrawables.append(resid, d); } } loaded = null; if (DEBUG) setWillNotDraw(false); } private static Bitmap convertToAlphaMask(Bitmap b) { Bitmap a = Bitmap.createBitmap(b.getWidth(), b.getHeight(), Bitmap.Config.ALPHA_8); Canvas c = new Canvas(a); Paint pt = new Paint(); pt.setColorFilter(new ColorMatrixColorFilter(MASK)); c.drawBitmap(b, 0.0f, 0.0f, pt); return a; } public void start() { if (!mStarted) { mStarted = true; fillFreeList(DURATION * 4); } mHandler.postDelayed(mJuggle, START_DELAY); } public void stop() { mStarted = false; mHandler.removeCallbacks(mJuggle); } int pick(int[] a) { return a[(int)(Math.random()*a.length)]; } <T> T pick(T[] a) { return a[(int)(Math.random()*a.length)]; } <T> T pick(SparseArray<T> sa) { return sa.valueAt((int)(Math.random()*sa.size())); } float[] hsv = new float[] { 0, 1f, .85f }; int random_color() { // return 0xFF000000 | (int) (Math.random() * (float) 0xFFFFFF); // totally random final int COLORS = 12; hsv[0] = irand(0,COLORS) * (360f/COLORS); return Color.HSVToColor(hsv); } @Override protected synchronized void onSizeChanged (int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mWidth == w && mHeight == h) return; final boolean wasStarted = mStarted; if (wasStarted) { stop(); } mWidth = w; mHeight = h; mCells = null; removeAllViewsInLayout(); mFreeList.clear(); mRows = mHeight / mCellSize; mColumns = mWidth / mCellSize; mCells = new View[mRows * mColumns]; if (DEBUG) Log.v(TAG, String.format("New dimensions: %dx%d", mColumns, mRows)); setScaleX(SCALE); setScaleY(SCALE); setTranslationX(0.5f * (mWidth - mCellSize * mColumns) * SCALE); setTranslationY(0.5f * (mHeight - mCellSize * mRows) * SCALE); for (int j=0; j<mRows; j++) { for (int i=0; i<mColumns; i++) { mFreeList.add(new Point(i,j)); } } if (wasStarted) { start(); } } public void fillFreeList() { fillFreeList(DURATION); } public synchronized void fillFreeList(int animationLen) { final Context ctx = getContext(); final FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(mCellSize, mCellSize); while (! mFreeList.isEmpty()) { Point pt = mFreeList.iterator().next(); mFreeList.remove(pt); final int i=pt.x; final int j=pt.y; if (mCells[j*mColumns+i] != null) continue; final ImageView v = new ImageView(ctx); v.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { place(v, true); postDelayed(new Runnable() { public void run() { fillFreeList(); } }, DURATION/2); } }); final int c = random_color(); v.setBackgroundColor(c); final float which = frand(); final Drawable d; if (which < 0.0005f) { d = mDrawables.get(pick(XXRARE_PASTRIES)); } else if (which < 0.005f) { d = mDrawables.get(pick(XRARE_PASTRIES)); } else if (which < 0.5f) { d = mDrawables.get(pick(RARE_PASTRIES)); } else if (which < 0.7f) { d = mDrawables.get(pick(PASTRIES)); } else { d = null; } if (d != null) { v.getOverlay().add(d); } lp.width = lp.height = mCellSize; addView(v, lp); place(v, pt, false); if (animationLen > 0) { final float s = (Integer) v.getTag(TAG_SPAN); v.setScaleX(0.5f * s); v.setScaleY(0.5f * s); v.setAlpha(0f); v.animate().withLayer().scaleX(s).scaleY(s).alpha(1f).setDuration(animationLen); } } } public void place(View v, boolean animate) { place(v, new Point(irand(0, mColumns), irand(0, mRows)), animate); } // we don't have .withLayer() on general Animators private final Animator.AnimatorListener makeHardwareLayerListener(final View v) { return new AnimatorListenerAdapter() { @Override public void onAnimationStart(Animator animator) { v.setLayerType(View.LAYER_TYPE_HARDWARE, null); v.buildLayer(); } @Override public void onAnimationEnd(Animator animator) { v.setLayerType(View.LAYER_TYPE_NONE, null); } }; } private final HashSet<View> tmpSet = new HashSet<View>(); public synchronized void place(View v, Point pt, boolean animate) { final int i = pt.x; final int j = pt.y; final float rnd = frand(); if (v.getTag(TAG_POS) != null) { for (final Point oc : getOccupied(v)) { mFreeList.add(oc); mCells[oc.y*mColumns + oc.x] = null; } } int scale = 1; if (rnd < PROB_4X) { if (!(i >= mColumns-3 || j >= mRows-3)) { scale = 4; } } else if (rnd < PROB_3X) { if (!(i >= mColumns-2 || j >= mRows-2)) { scale = 3; } } else if (rnd < PROB_2X) { if (!(i == mColumns-1 || j == mRows-1)) { scale = 2; } } v.setTag(TAG_POS, pt); v.setTag(TAG_SPAN, scale); tmpSet.clear(); final Point[] occupied = getOccupied(v); for (final Point oc : occupied) { final View squatter = mCells[oc.y*mColumns + oc.x]; if (squatter != null) { tmpSet.add(squatter); } } for (final View squatter : tmpSet) { for (final Point sq : getOccupied(squatter)) { mFreeList.add(sq); mCells[sq.y*mColumns + sq.x] = null; } if (squatter != v) { squatter.setTag(TAG_POS, null); if (animate) { squatter.animate().withLayer() .scaleX(0.5f).scaleY(0.5f).alpha(0) .setDuration(DURATION) .setInterpolator(new AccelerateInterpolator()) .setListener(new Animator.AnimatorListener() { public void onAnimationStart(Animator animator) { } public void onAnimationEnd(Animator animator) { removeView(squatter); } public void onAnimationCancel(Animator animator) { } public void onAnimationRepeat(Animator animator) { } }) .start(); } else { removeView(squatter); } } } for (final Point oc : occupied) { mCells[oc.y*mColumns + oc.x] = v; mFreeList.remove(oc); } final float rot = (float)irand(0, 4) * 90f; if (animate) { v.bringToFront(); AnimatorSet set1 = new AnimatorSet(); set1.playTogether( ObjectAnimator.ofFloat(v, View.SCALE_X, (float) scale), ObjectAnimator.ofFloat(v, View.SCALE_Y, (float) scale) ); set1.setInterpolator(new AnticipateOvershootInterpolator()); set1.setDuration(DURATION); AnimatorSet set2 = new AnimatorSet(); set2.playTogether( ObjectAnimator.ofFloat(v, View.ROTATION, rot), ObjectAnimator.ofFloat(v, View.X, i* mCellSize + (scale-1) * mCellSize /2), ObjectAnimator.ofFloat(v, View.Y, j* mCellSize + (scale-1) * mCellSize /2) ); set2.setInterpolator(new DecelerateInterpolator()); set2.setDuration(DURATION); set1.addListener(makeHardwareLayerListener(v)); set1.start(); set2.start(); } else { v.setX(i * mCellSize + (scale-1) * mCellSize /2); v.setY(j * mCellSize + (scale-1) * mCellSize /2); v.setScaleX((float) scale); v.setScaleY((float) scale); v.setRotation(rot); } } private Point[] getOccupied(View v) { final int scale = (Integer) v.getTag(TAG_SPAN); final Point pt = (Point)v.getTag(TAG_POS); if (pt == null || scale == 0) return new Point[0]; final Point[] result = new Point[scale * scale]; int p=0; for (int i=0; i<scale; i++) { for (int j=0; j<scale; j++) { result[p++] = new Point(pt.x + i, pt.y + j); } } return result; } static float frand() { return (float)(Math.random()); } static float frand(float a, float b) { return (frand() * (b-a) + a); } static int irand(int a, int b) { return (int)(frand(a, b)); } @Override public void onDraw(Canvas c) { super.onDraw(c); if (!DEBUG) return; Paint pt = new Paint(); pt.setStyle(Paint.Style.STROKE); pt.setColor(0xFFCCCCCC); pt.setStrokeWidth(2.0f); final Rect check = new Rect(); final int N = getChildCount(); for (int i = 0; i < N; i++) { View stone = getChildAt(i); stone.getHitRect(check); c.drawRect(check, pt); } } public static class RescalingContainer extends FrameLayout { private DessertCaseView mView; private float mDarkness; public RescalingContainer(Context context) { super(context); setSystemUiVisibility(0 | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY ); } public void setView(DessertCaseView v) { addView(v); mView = v; } @Override protected void onLayout (boolean changed, int left, int top, int right, int bottom) { final float w = right-left; final float h = bottom-top; final int w2 = (int) (w / mView.SCALE / 2); final int h2 = (int) (h / mView.SCALE / 2); final int cx = (int) (left + w * 0.5f); final int cy = (int) (top + h * 0.5f); mView.layout(cx - w2, cy - h2, cx + w2, cy + h2); } public void setDarkness(float p) { mDarkness = p; getDarkness(); final int x = (int) (p * 0xff); setBackgroundColor(x << 24 & 0xFF000000); } public float getDarkness() { return mDarkness; } } }