Home | History | Annotate | Download | only in systemui
      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