14e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler/*);
24e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler * Copyright (C) 2011 The Android Open Source Project
34e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler *
44e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler * Licensed under the Apache License, Version 2.0 (the "License");
54e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler * you may not use this file except in compliance with the License.
64e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler * You may obtain a copy of the License at
74e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler *
84e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler *      http://www.apache.org/licenses/LICENSE-2.0
94e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler *
104e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler * Unless required by applicable law or agreed to in writing, software
114e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler * distributed under the License is distributed on an "AS IS" BASIS,
124e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
134e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler * See the License for the specific language governing permissions and
144e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler * limitations under the License.
154e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler */
164e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
174e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler// TODO:
184e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler// background stellar matter:
194e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler//  - add some slow horizontal parallax motion, or perhaps veeeeery gradual outward drift
204e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
214e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerpackage com.android.launcher2;
224e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
234e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.animation.AnimatorSet;
244e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.animation.ObjectAnimator;
254e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.animation.TimeAnimator;
26332ffed2dfce15e2b1da71bf227d1bd5d5c016aeDaniel Sandlerimport android.app.Activity;
274e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.content.ComponentName;
284e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.content.Context;
294e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.content.Intent;
304e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.graphics.Bitmap;
314e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.graphics.Point;
324e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.graphics.Rect;
334e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.os.Handler;
344e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.util.AttributeSet;
354e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.util.DisplayMetrics;
364e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.view.MotionEvent;
374e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.view.View;
384e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.view.ViewGroup;
394e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.widget.FrameLayout;
404e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport android.widget.ImageView;
414e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
424e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandlerimport com.android.launcher.R;
434e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
4492f3d46cf35e78891f73226e81f8ca7e9a7f4d92Michael Jurkaimport java.util.HashMap;
4592f3d46cf35e78891f73226e81f8ca7e9a7f4d92Michael Jurkaimport java.util.Random;
4692f3d46cf35e78891f73226e81f8ca7e9a7f4d92Michael Jurka
47332ffed2dfce15e2b1da71bf227d1bd5d5c016aeDaniel Sandlerpublic class RocketLauncher extends Activity {
484e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler    public static final boolean ROCKET_LAUNCHER = true;
494e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
504e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler    public static class Board extends FrameLayout
514e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler    {
524e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        public static final boolean FIXED_STARS = true;
534e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        public static final boolean FLYING_STARS = true;
544e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        public static final int NUM_ICONS = 20;
554e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
564e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        public static final float MANEUVERING_THRUST_SCALE = 0.1f; // tenth speed
574e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        private boolean mManeuveringThrusters = false;
584e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        private float mSpeedScale = 1.0f;
594e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
604e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        public static final int LAUNCH_ZOOM_TIME = 400; // ms
614e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
624e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        HashMap<ComponentName, Bitmap> mIcons;
634e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        ComponentName[] mComponentNames;
644e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
654e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        static Random sRNG = new Random();
664e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
674e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        static float lerp(float a, float b, float f) {
684e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            return (b-a)*f + a;
694e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
704e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
714e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        static float randfrange(float a, float b) {
724e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            return lerp(a, b, sRNG.nextFloat());
734e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
744e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
754e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        static int randsign() {
764e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            return sRNG.nextBoolean() ? 1 : -1;
774e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
784e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
794e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        static <E> E pick(E[] array) {
804e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            if (array.length == 0) return null;
814e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            return array[sRNG.nextInt(array.length)];
824e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
834e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
844e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        public class FlyingIcon extends ImageView {
854e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public static final float VMAX = 1000.0f;
864e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public static final float VMIN = 100.0f;
874e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public static final float ANGULAR_VMAX = 45f;
884e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public static final float ANGULAR_VMIN = 0f;
894e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public static final float SCALE_MIN = 0.5f;
904e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public static final float SCALE_MAX = 4f;
914e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
924e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public float v, vr;
934e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
944e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public final float[] hsv = new float[3];
954e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
964e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public float angle, anglex, angley;
974e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public float fuse;
984e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public float dist;
994e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public float endscale;
1004e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public float boardCenterX, boardCenterY;
1014e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
1024e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public ComponentName component;
1034e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
1044e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public FlyingIcon(Context context, AttributeSet as) {
1054e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                super(context, as);
1064e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                setLayerType(View.LAYER_TYPE_HARDWARE, null);
1074e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
1084e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                setBackgroundResource(R.drawable.flying_icon_bg);
1094e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                //android.util.Log.d("RocketLauncher", "ctor: " + this);
1104e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                hsv[1] = 1f;
1114e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                hsv[2] = 1f;
1124e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
1134e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
1144e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            @Override
1154e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public boolean onTouchEvent(MotionEvent event) {
1164e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                if (!mManeuveringThrusters || component == null) {
1174e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    return false;
1184e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                }
1194e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                if (getAlpha() < 0.5f) {
1204e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    setPressed(false);
1214e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    return false;
1224e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                }
1234e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
1244e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                switch (event.getAction()) {
1254e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    case MotionEvent.ACTION_DOWN:
1264e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        setPressed(true);
1274e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        Board.this.resetWarpTimer();
1284e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        break;
1294e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    case MotionEvent.ACTION_MOVE:
1304e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        final Rect hit = new Rect();
1314e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        final Point offset = new Point();
1324e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        getGlobalVisibleRect(hit, offset);
1334e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        final int globx = (int) event.getX() + offset.x;
1344e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        final int globy = (int) event.getY() + offset.y;
1354e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        setPressed(hit.contains(globx, globy));
1364e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        Board.this.resetWarpTimer();
1374e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        break;
1384e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    case MotionEvent.ACTION_UP:
1394e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        if (isPressed()) {
1404e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            setPressed(false);
1414e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            postDelayed(new Runnable() {
1424e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                                public void run() {
1434e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                                    try {
1444e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                                        getContext().startActivity(new Intent(Intent.ACTION_MAIN)
1454e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
1464e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                                            .setComponent(component));
1474e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                                    } catch (android.content.ActivityNotFoundException e) {
1484e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                                    } catch (SecurityException e) {
1494e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                                    }
1504e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                                }
1514e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            }, LAUNCH_ZOOM_TIME);
1524e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            endscale = 0;
1534e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            AnimatorSet s = new AnimatorSet();
1544e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            s.playTogether(
1554e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                                ObjectAnimator.ofFloat(this, "scaleX", 15f),
1564e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                                ObjectAnimator.ofFloat(this, "scaleY", 15f),
1574e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                                ObjectAnimator.ofFloat(this, "alpha", 0f)
1584e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            );
1594e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
1604e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            // make sure things are still moving until the very last instant the
1614e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            // activity is visible
1624e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            s.setDuration((int)(LAUNCH_ZOOM_TIME * 1.25));
1634e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            s.setInterpolator(new android.view.animation.AccelerateInterpolator(3));
1644e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            s.start();
1654e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        }
1664e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        break;
1674e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                }
1684e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                return true;
1694e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
1704e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
1714e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public String toString() {
1724e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                return String.format("<'%s' @ (%.1f, %.1f) v=%.1f a=%.1f dist/fuse=%.1f/%.1f>",
1734e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        "icon", getX(), getY(), v, angle, dist, fuse);
1744e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
1754e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
1764e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public void randomizeIcon() {
1774e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                component = pick(mComponentNames);
1784e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                setImageBitmap(mIcons.get(component));
1794e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
1804e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
1814e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public void randomize() {
1824e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                v = randfrange(VMIN, VMAX);
1834e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                angle = randfrange(0, 360f);
1844e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                anglex = (float) Math.sin(angle / 180. * Math.PI);
1854e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                angley = (float) Math.cos(angle / 180. * Math.PI);
1864e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                vr = randfrange(ANGULAR_VMIN, ANGULAR_VMAX) * randsign();
1874e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                endscale = randfrange(SCALE_MIN, SCALE_MAX);
1884e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
1894e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                randomizeIcon();
1904e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
1914e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public void reset() {
1924e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                randomize();
1934e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                boardCenterX = (Board.this.getWidth() - getWidth()) / 2;
1944e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                boardCenterY = (Board.this.getHeight() - getHeight()) / 2;
1954e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                setX(boardCenterX);
1964e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                setY(boardCenterY);
1974e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                fuse = (float) Math.max(boardCenterX, boardCenterY);
1984e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                setRotation(180-angle);
1994e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                setScaleX(0f);
2004e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                setScaleY(0f);
2014e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                dist = 0;
2024e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                setAlpha(0f);
2034e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
2044e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public void update(float dt) {
2054e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                dist += v * dt;
2064e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                setX(getX() + anglex * v * dt);
2074e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                setY(getY() + angley * v * dt);
2084e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                //setRotation(getRotation() + vr * dt);
2094e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                if (endscale > 0) {
2104e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    float scale = lerp(0, endscale, (float) Math.sqrt(dist / fuse));
2114e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        setScaleX(scale * lerp(1f, 0.75f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3)));
2124e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        setScaleY(scale * lerp(1f, 1.5f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3)));
2134e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    final float q1 = fuse*0.15f;
2144e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    final float q4 = fuse*0.75f;
2154e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    if (dist < q1) {
2164e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        setAlpha((float) Math.sqrt(dist/q1));
2174e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    } else if (dist > q4) {
2184e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        setAlpha((dist >= fuse) ? 0f : (1f-(float)Math.pow((dist-q4)/(fuse-q4),2)));
2194e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    } else {
2204e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        setAlpha(1f);
2214e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    }
2224e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                }
2234e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
2244e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
2254e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
2264e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        public class FlyingStar extends FlyingIcon {
2274e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public FlyingStar(Context context, AttributeSet as) {
2284e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                super(context, as);
2294e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
2304e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public void randomizeIcon() {
2314e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                setImageResource(R.drawable.widget_resize_handle_bottom);
2324e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
2334e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public void randomize() {
2344e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                super.randomize();
2354e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                v = randfrange(VMAX*0.75f, VMAX*2f); // fasticate
2364e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                endscale = randfrange(1f, 2f); // ensmallen
2374e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
2384e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
2394e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
2404e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        TimeAnimator mAnim;
2414e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
2424e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        public Board(Context context, AttributeSet as) {
2434e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            super(context, as);
2444e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
2454e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            setBackgroundColor(0xFF000000);
2464e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
2474e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            LauncherApplication app = (LauncherApplication)context.getApplicationContext();
2484e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            mIcons = app.getIconCache().getAllIcons();
2494e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            mComponentNames = new ComponentName[mIcons.size()];
2504e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            mComponentNames = mIcons.keySet().toArray(mComponentNames);
2514e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
2524e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
2534e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        private void reset() {
2544e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            removeAllViews();
2554e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
2564e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            final ViewGroup.LayoutParams wrap = new ViewGroup.LayoutParams(
2574e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        ViewGroup.LayoutParams.WRAP_CONTENT,
2584e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        ViewGroup.LayoutParams.WRAP_CONTENT);
2594e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
2604e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            if (FIXED_STARS) {
2614e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                for(int i=0; i<20; i++) {
2624e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    ImageView fixedStar = new ImageView(getContext(), null);
2634e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    fixedStar.setImageResource(R.drawable.widget_resize_handle_bottom);
2644e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    final float s = randfrange(0.25f, 0.75f);
2654e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    fixedStar.setScaleX(s);
2664e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    fixedStar.setScaleY(s);
2674e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    fixedStar.setAlpha(0.75f);
2684e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    addView(fixedStar, wrap);
2694e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    fixedStar.setX(randfrange(0, getWidth()));
2704e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    fixedStar.setY(randfrange(0, getHeight()));
2714e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                }
2724e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
2734e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
2744e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            for(int i=0; i<NUM_ICONS*2; i++) {
2754e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                FlyingIcon nv = (FLYING_STARS && (i < NUM_ICONS))
2764e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    ? new FlyingStar(getContext(), null)
2774e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    : new FlyingIcon(getContext(), null);
2784e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                addView(nv, wrap);
2794e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                nv.reset();
2804e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
2814e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
2824e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            mAnim = new TimeAnimator();
2834e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            mAnim.setTimeListener(new TimeAnimator.TimeListener() {
2844e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
2854e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    // setRotation(totalTime * 0.01f); // not as cool as you would think
2864e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
2874e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    final int START_ZOOM_TIME = 3000;
2884e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    if (totalTime < START_ZOOM_TIME) {
2894e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        final float x = totalTime/(float)START_ZOOM_TIME;
2904e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        final float s = 1f-(float)Math.pow(x-1, 4);
2914e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        setScaleX(s); setScaleY(s);
2924e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    } else {
2934e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        setScaleX(1.0f); setScaleY(1.0f);
2944e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    }
2954e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
2964e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    if (mManeuveringThrusters) {
2974e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        if (mSpeedScale > MANEUVERING_THRUST_SCALE) {
2984e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            mSpeedScale -= (2*deltaTime/1000f);
2994e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        }
3004e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        if (mSpeedScale < MANEUVERING_THRUST_SCALE) {
3014e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            mSpeedScale = MANEUVERING_THRUST_SCALE;
3024e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        }
3034e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    } else {
3044e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        if (mSpeedScale < 1.0f) {
3054e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            mSpeedScale += (deltaTime/1000f);
3064e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        }
3074e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        if (mSpeedScale > 1.0f) {
3084e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            mSpeedScale = 1.0f;
3094e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        }
3104e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    }
3114e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
3124e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    for (int i=0; i<getChildCount(); i++) {
3134e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        View v = getChildAt(i);
3144e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        if (!(v instanceof FlyingIcon)) continue;
3154e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        FlyingIcon nv = (FlyingIcon) v;
3164e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        nv.update(deltaTime / 1000f * mSpeedScale);
3174e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        final float scaledWidth = nv.getWidth() * nv.getScaleX();
3184e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        final float scaledHeight = nv.getHeight() * nv.getScaleY();
3194e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        if (   nv.getX() + scaledWidth < 0
3204e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            || nv.getX() - scaledWidth > getWidth()
3214e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            || nv.getY() + scaledHeight < 0
3224e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            || nv.getY() - scaledHeight > getHeight())
3234e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        {
3244e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                            nv.reset();
3254e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                        }
3264e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    }
3274e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                }
3284e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            });
3294e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
3304e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
3314e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        @Override
3324e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        protected void onAttachedToWindow() {
3334e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            super.onAttachedToWindow();
3344e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            setLayerType(View.LAYER_TYPE_HARDWARE, null);
3354e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
3364e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
3374e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            reset();
3384e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            mAnim.start();
3394e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
3404e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
3414e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        protected void onSizeChanged (int w, int h, int oldw, int oldh) {
3424e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            super.onSizeChanged(w,h,oldw,oldh);
3434e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            mAnim.cancel();
3444e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            reset();
3454e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            mAnim.start();
3464e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
3474e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
3484e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
3494e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        @Override
3504e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        protected void onDetachedFromWindow() {
3514e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            super.onDetachedFromWindow();
3524e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            mAnim.cancel();
3534e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
3544e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
3554e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        @Override
3564e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        public boolean isOpaque() {
3574e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            return true;
3584e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
3594e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
3604e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        @Override
3614e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        public boolean onInterceptTouchEvent(MotionEvent e) {
3624e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            // we want to eat touch events ourselves if we're in warp speed
3634e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            return (!(ROCKET_LAUNCHER && mManeuveringThrusters));
3644e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
3654e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
3664e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        final Runnable mEngageWarp = new Runnable() {
3674e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            @Override
3684e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            public void run() {
3694e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                mManeuveringThrusters = false;
3704e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
3714e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        };
3724e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        public void resetWarpTimer() {
3734e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            final Handler h = getHandler();
3744e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            h.removeCallbacks(mEngageWarp);
3754e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            h.postDelayed(mEngageWarp, 5000);
3764e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
3774e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
3784e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        @Override
3794e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        public boolean onTouchEvent(MotionEvent event) {
3804e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            if (!ROCKET_LAUNCHER) {
3814e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                return true;
3824e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
3834e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
3844e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            if (event.getAction() == MotionEvent.ACTION_DOWN) {
3854e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                if (!mManeuveringThrusters) {
3864e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    mManeuveringThrusters = true;
3874e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    resetWarpTimer();
3884e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                    return true;
3894e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler                }
3904e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            }
3914e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
3924e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            return false;
3934e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
3944e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler    }
3954e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
3964e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler    @Override
3974e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler    public void onStart() {
3984e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        super.onStart();
3994e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
4004e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        DisplayMetrics metrics = new DisplayMetrics();
4014e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        getWindowManager().getDefaultDisplay().getMetrics(metrics);
4024e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        final int longside = metrics.widthPixels > metrics.heightPixels
4034e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            ? metrics.widthPixels : metrics.heightPixels;
4044e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
4054e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        Board b = new Board(this, null);
4064e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        setContentView(b, new ViewGroup.LayoutParams(longside, longside));
4074e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        b.setX((metrics.widthPixels - longside) / 2);
4084e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        b.setY((metrics.heightPixels - longside) / 2);
4094e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler    }
4104e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler
4114e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler    @Override
4124e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler    public void onUserInteraction() {
4134e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        if (!ROCKET_LAUNCHER) {
4144e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler            finish();
4154e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler        }
4164e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler    }
4174e1cd23a51f1d83c1fe5d3eed536641087671db7Daniel Sandler}
418