Merge "Rocket Launcher!"
diff --git a/Android.mk b/Android.mk
index 64008ba..844f052 100644
--- a/Android.mk
+++ b/Android.mk
@@ -21,7 +21,7 @@
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_STATIC_JAVA_LIBRARIES := android-common
+LOCAL_STATIC_JAVA_LIBRARIES := android-common android-support-v13
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src)
 
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 01e2b77..5206896 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -100,6 +100,18 @@
                 android:resource="@xml/wallpaper_picker_preview" />
         </activity>
 
+        <activity android:name="com.android.launcher2.RocketLauncher"
+            android:label="@string/dream_name"
+            android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
+            android:hardwareAccelerated="true"
+            >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.DREAM" />
+            </intent-filter>
+        </activity>
+
         <!-- Intent received used to install shortcuts from other applications -->
         <receiver
             android:name="com.android.launcher2.InstallShortcutReceiver"
diff --git a/res/drawable/flying_icon_bg.xml b/res/drawable/flying_icon_bg.xml
new file mode 100644
index 0000000..affd975
--- /dev/null
+++ b/res/drawable/flying_icon_bg.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true" android:drawable="@drawable/homescreen_small_green" />
+    <item android:drawable="@android:color/transparent" />
+</selector>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7e8bec0..75038ae 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -244,4 +244,7 @@
 
     <!--  Text to inform the user that they can't uninstall a system application -->
     <string name="uninstall_system_app_text">This is a system application and cannot be uninstalled.</string>
+
+    <!-- Title of the Android Dreams (screensaver) module -->
+    <string name="dream_name">Rocket Launcher</string>
 </resources>
diff --git a/src/com/android/launcher2/IconCache.java b/src/com/android/launcher2/IconCache.java
index 7e37afe..5c07cfb 100644
--- a/src/com/android/launcher2/IconCache.java
+++ b/src/com/android/launcher2/IconCache.java
@@ -24,6 +24,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
+import android.util.Pair;
 import android.util.DisplayMetrics;
 
 import java.util.HashMap;
@@ -187,4 +188,16 @@
         }
         return entry;
     }
+
+    public HashMap<ComponentName,Bitmap> getAllIcons() {
+        synchronized (mCache) {
+            HashMap<ComponentName,Bitmap> set = new HashMap<ComponentName,Bitmap>();
+            int i = 0;
+            for (ComponentName cn : mCache.keySet()) {
+                final CacheEntry e = mCache.get(cn);
+                set.put(cn, e.icon);
+            }
+            return set;
+        }
+    }
 }
diff --git a/src/com/android/launcher2/RocketLauncher.java b/src/com/android/launcher2/RocketLauncher.java
new file mode 100644
index 0000000..1c6510f
--- /dev/null
+++ b/src/com/android/launcher2/RocketLauncher.java
@@ -0,0 +1,426 @@
+/*);
+ * Copyright (C) 2011 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.
+ */
+
+// TODO:
+// background stellar matter:
+//  - add some slow horizontal parallax motion, or perhaps veeeeery gradual outward drift
+
+package com.android.launcher2;
+
+import android.animation.AnimatorSet;
+import android.animation.PropertyValuesHolder;
+import android.animation.ObjectAnimator;
+import android.animation.TimeAnimator;
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.support.v13.dreams.BasicDream;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Pair;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import java.util.HashMap;
+import java.util.Random;
+
+import com.android.launcher.R;
+
+public class RocketLauncher extends BasicDream {
+    public static final boolean ROCKET_LAUNCHER = true;
+
+    public static class Board extends FrameLayout
+    {
+        public static final boolean FIXED_STARS = true;
+        public static final boolean FLYING_STARS = true;
+        public static final int NUM_ICONS = 20;
+
+        public static final float MANEUVERING_THRUST_SCALE = 0.1f; // tenth speed
+        private boolean mManeuveringThrusters = false;
+        private float mSpeedScale = 1.0f;
+
+        public static final int LAUNCH_ZOOM_TIME = 400; // ms
+
+        HashMap<ComponentName, Bitmap> mIcons;
+        ComponentName[] mComponentNames;
+
+        static Random sRNG = new Random();
+
+        static float lerp(float a, float b, float f) {
+            return (b-a)*f + a;
+        }
+
+        static float randfrange(float a, float b) {
+            return lerp(a, b, sRNG.nextFloat());
+        }
+
+        static int randsign() {
+            return sRNG.nextBoolean() ? 1 : -1;
+        }
+
+        static <E> E pick(E[] array) {
+            if (array.length == 0) return null;
+            return array[sRNG.nextInt(array.length)];
+        }
+
+        public class FlyingIcon extends ImageView {
+            public static final float VMAX = 1000.0f;
+            public static final float VMIN = 100.0f;
+            public static final float ANGULAR_VMAX = 45f;
+            public static final float ANGULAR_VMIN = 0f;
+            public static final float SCALE_MIN = 0.5f;
+            public static final float SCALE_MAX = 4f;
+
+            public float v, vr;
+
+            public final float[] hsv = new float[3];
+
+            public float angle, anglex, angley;
+            public float fuse;
+            public float dist;
+            public float endscale;
+            public float boardCenterX, boardCenterY;
+
+            public ComponentName component;
+
+            public FlyingIcon(Context context, AttributeSet as) {
+                super(context, as);
+                setLayerType(View.LAYER_TYPE_HARDWARE, null);
+
+                setBackgroundResource(R.drawable.flying_icon_bg);
+                //android.util.Log.d("RocketLauncher", "ctor: " + this);
+                hsv[1] = 1f;
+                hsv[2] = 1f;
+            }
+
+            @Override
+            public boolean onTouchEvent(MotionEvent event) {
+                if (!mManeuveringThrusters || component == null) {
+                    return false;
+                }
+                if (getAlpha() < 0.5f) {
+                    setPressed(false);
+                    return false;
+                }
+
+                switch (event.getAction()) {
+                    case MotionEvent.ACTION_DOWN:
+                        setPressed(true);
+                        Board.this.resetWarpTimer();
+                        break;
+                    case MotionEvent.ACTION_MOVE:
+                        final Rect hit = new Rect();
+                        final Point offset = new Point();
+                        getGlobalVisibleRect(hit, offset);
+                        final int globx = (int) event.getX() + offset.x;
+                        final int globy = (int) event.getY() + offset.y;
+                        setPressed(hit.contains(globx, globy));
+                        Board.this.resetWarpTimer();
+                        break;
+                    case MotionEvent.ACTION_UP:
+                        if (isPressed()) {
+                            setPressed(false);
+                            postDelayed(new Runnable() {
+                                public void run() {
+                                    try {
+                                        getContext().startActivity(new Intent(Intent.ACTION_MAIN)
+                                            .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                                            .setComponent(component));
+                                    } catch (android.content.ActivityNotFoundException e) {
+                                    } catch (SecurityException e) {
+                                    }
+                                }
+                            }, LAUNCH_ZOOM_TIME);
+                            endscale = 0;
+                            AnimatorSet s = new AnimatorSet();
+                            s.playTogether(
+                                ObjectAnimator.ofFloat(this, "scaleX", 15f),
+                                ObjectAnimator.ofFloat(this, "scaleY", 15f),
+                                ObjectAnimator.ofFloat(this, "alpha", 0f)
+                            );
+
+                            // make sure things are still moving until the very last instant the
+                            // activity is visible
+                            s.setDuration((int)(LAUNCH_ZOOM_TIME * 1.25));
+                            s.setInterpolator(new android.view.animation.AccelerateInterpolator(3));
+                            s.start();
+                        }
+                        break;
+                }
+                return true;
+            }
+
+            public String toString() {
+                return String.format("<'%s' @ (%.1f, %.1f) v=%.1f a=%.1f dist/fuse=%.1f/%.1f>",
+                        "icon", getX(), getY(), v, angle, dist, fuse);
+            }
+
+            public void randomizeIcon() {
+                component = pick(mComponentNames);
+                setImageBitmap(mIcons.get(component));
+            }
+
+            public void randomize() {
+                v = randfrange(VMIN, VMAX);
+                angle = randfrange(0, 360f);
+                anglex = (float) Math.sin(angle / 180. * Math.PI);
+                angley = (float) Math.cos(angle / 180. * Math.PI);
+                vr = randfrange(ANGULAR_VMIN, ANGULAR_VMAX) * randsign();
+                endscale = randfrange(SCALE_MIN, SCALE_MAX);
+
+                randomizeIcon();
+            }
+            public void reset() {
+                randomize();
+                boardCenterX = (Board.this.getWidth() - getWidth()) / 2;
+                boardCenterY = (Board.this.getHeight() - getHeight()) / 2;
+                setX(boardCenterX);
+                setY(boardCenterY);
+                fuse = (float) Math.max(boardCenterX, boardCenterY);
+                setRotation(180-angle);
+                setScaleX(0f);
+                setScaleY(0f);
+                dist = 0;
+                setAlpha(0f);
+            }
+            public void update(float dt) {
+                dist += v * dt;
+                setX(getX() + anglex * v * dt);
+                setY(getY() + angley * v * dt);
+                //setRotation(getRotation() + vr * dt);
+                if (endscale > 0) {
+                    float scale = lerp(0, endscale, (float) Math.sqrt(dist / fuse));
+                        setScaleX(scale * lerp(1f, 0.75f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3)));
+                        setScaleY(scale * lerp(1f, 1.5f, (float) Math.pow((v-VMIN)/(VMAX-VMIN),3)));
+                    final float q1 = fuse*0.15f;
+                    final float q4 = fuse*0.75f;
+                    if (dist < q1) {
+                        setAlpha((float) Math.sqrt(dist/q1));
+                    } else if (dist > q4) {
+                        setAlpha((dist >= fuse) ? 0f : (1f-(float)Math.pow((dist-q4)/(fuse-q4),2)));
+                    } else {
+                        setAlpha(1f);
+                    }
+                }
+            }
+        }
+
+        public class FlyingStar extends FlyingIcon {
+            public FlyingStar(Context context, AttributeSet as) {
+                super(context, as);
+            }
+            public void randomizeIcon() {
+                setImageResource(R.drawable.widget_resize_handle_bottom);
+            }
+            public void randomize() {
+                super.randomize();
+                v = randfrange(VMAX*0.75f, VMAX*2f); // fasticate
+                endscale = randfrange(1f, 2f); // ensmallen
+            }
+        }
+
+        TimeAnimator mAnim;
+
+        public Board(Context context, AttributeSet as) {
+            super(context, as);
+
+            setBackgroundColor(0xFF000000);
+
+            LauncherApplication app = (LauncherApplication)context.getApplicationContext();
+            mIcons = app.getIconCache().getAllIcons();
+            mComponentNames = new ComponentName[mIcons.size()];
+            mComponentNames = mIcons.keySet().toArray(mComponentNames);
+        }
+
+        private void reset() {
+            removeAllViews();
+
+            final ViewGroup.LayoutParams wrap = new ViewGroup.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT);
+
+            if (FIXED_STARS) {
+                for(int i=0; i<20; i++) {
+                    ImageView fixedStar = new ImageView(getContext(), null);
+                    fixedStar.setImageResource(R.drawable.widget_resize_handle_bottom);
+                    final float s = randfrange(0.25f, 0.75f);
+                    fixedStar.setScaleX(s);
+                    fixedStar.setScaleY(s);
+                    fixedStar.setAlpha(0.75f);
+                    addView(fixedStar, wrap);
+                    fixedStar.setX(randfrange(0, getWidth()));
+                    fixedStar.setY(randfrange(0, getHeight()));
+                }
+            }
+
+            for(int i=0; i<NUM_ICONS*2; i++) {
+                FlyingIcon nv = (FLYING_STARS && (i < NUM_ICONS))
+                    ? new FlyingStar(getContext(), null)
+                    : new FlyingIcon(getContext(), null);
+                addView(nv, wrap);
+                nv.reset();
+            }
+
+            mAnim = new TimeAnimator();
+            mAnim.setTimeListener(new TimeAnimator.TimeListener() {
+                public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+                    // setRotation(totalTime * 0.01f); // not as cool as you would think
+
+                    final int START_ZOOM_TIME = 3000;
+                    if (totalTime < START_ZOOM_TIME) {
+                        final float x = totalTime/(float)START_ZOOM_TIME;
+                        final float s = 1f-(float)Math.pow(x-1, 4);
+                        setScaleX(s); setScaleY(s);
+                    } else {
+                        setScaleX(1.0f); setScaleY(1.0f);
+                    }
+
+                    if (mManeuveringThrusters) {
+                        if (mSpeedScale > MANEUVERING_THRUST_SCALE) {
+                            mSpeedScale -= (2*deltaTime/1000f);
+                        }
+                        if (mSpeedScale < MANEUVERING_THRUST_SCALE) {
+                            mSpeedScale = MANEUVERING_THRUST_SCALE;
+                        }
+                    } else {
+                        if (mSpeedScale < 1.0f) {
+                            mSpeedScale += (deltaTime/1000f);
+                        }
+                        if (mSpeedScale > 1.0f) {
+                            mSpeedScale = 1.0f;
+                        }
+                    }
+
+                    for (int i=0; i<getChildCount(); i++) {
+                        View v = getChildAt(i);
+                        if (!(v instanceof FlyingIcon)) continue;
+                        FlyingIcon nv = (FlyingIcon) v;
+                        nv.update(deltaTime / 1000f * mSpeedScale);
+                        final float scaledWidth = nv.getWidth() * nv.getScaleX();
+                        final float scaledHeight = nv.getHeight() * nv.getScaleY();
+                        if (   nv.getX() + scaledWidth < 0
+                            || nv.getX() - scaledWidth > getWidth()
+                            || nv.getY() + scaledHeight < 0 
+                            || nv.getY() - scaledHeight > getHeight())
+                        {
+                            nv.reset();
+                        }
+                    }
+                }
+            });
+        }
+
+        @Override
+        protected void onAttachedToWindow() {
+            super.onAttachedToWindow();
+            setLayerType(View.LAYER_TYPE_HARDWARE, null);
+            setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
+
+            reset();
+            mAnim.start();
+        }
+
+        protected void onSizeChanged (int w, int h, int oldw, int oldh) {
+            super.onSizeChanged(w,h,oldw,oldh);
+            mAnim.cancel();
+            reset();
+            mAnim.start();
+        }
+
+
+        @Override
+        protected void onDetachedFromWindow() {
+            super.onDetachedFromWindow();
+            mAnim.cancel();
+        }
+
+        @Override
+        public boolean isOpaque() {
+            return true;
+        }
+
+        @Override
+        public boolean onInterceptTouchEvent(MotionEvent e) {
+            // we want to eat touch events ourselves if we're in warp speed
+            return (!(ROCKET_LAUNCHER && mManeuveringThrusters));
+        }
+
+        final Runnable mEngageWarp = new Runnable() {
+            @Override
+            public void run() {
+                mManeuveringThrusters = false;
+            }
+        };
+        public void resetWarpTimer() {
+            final Handler h = getHandler();
+            h.removeCallbacks(mEngageWarp);
+            h.postDelayed(mEngageWarp, 5000);
+        }
+
+        @Override
+        public boolean onTouchEvent(MotionEvent event) {
+            if (!ROCKET_LAUNCHER) {
+                return true;
+            }
+
+            if (event.getAction() == MotionEvent.ACTION_DOWN) {
+                if (!mManeuveringThrusters) {
+                    mManeuveringThrusters = true;
+                    resetWarpTimer();
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+
+        DisplayMetrics metrics = new DisplayMetrics();
+        getWindowManager().getDefaultDisplay().getMetrics(metrics);
+        final int longside = metrics.widthPixels > metrics.heightPixels 
+            ? metrics.widthPixels : metrics.heightPixels;
+
+        Board b = new Board(this, null);
+        setContentView(b, new ViewGroup.LayoutParams(longside, longside));
+        b.setX((metrics.widthPixels - longside) / 2);
+        b.setY((metrics.heightPixels - longside) / 2);
+    }
+
+    @Override
+    public void onUserInteraction() {
+        if (!ROCKET_LAUNCHER) {
+            finish();
+        }
+    }
+}