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