Index: AndroidManifest.xml
===================================================================
--- AndroidManifest.xml	(revision 1a91f0dcdf2b97c5a72bf534516f7a8af58cbdb2)
+++ AndroidManifest.xml	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
@@ -1,18 +1,13 @@
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-      package="com.example.helloandroid"
-      android:versionCode="1"
-      android:versionName="1.0">
-    <application android:icon="@drawable/icon" android:label="@string/app_name">
-        <activity android:name=".HelloAndroid"
-                  android:label="@string/app_name">
+    package="com.example.helloandroid">
+    <application android:icon="@drawable/app_lunar_lander" android:label="@string/app_name">
+        <activity android:name="Game">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
-        </activity>
-
+		</activity>
     </application>
     <uses-sdk android:minSdkVersion="7" />
-
-</manifest> 
+</manifest>
Index: res/layout/lunar_layout.xml
===================================================================
--- res/layout/lunar_layout.xml	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
+++ res/layout/lunar_layout.xml	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent">
+    
+    <com.example.helloandroid.LunarView
+      android:id="@+id/lunar"
+      android:layout_width="fill_parent"
+      android:layout_height="fill_parent"/>
+    
+    <RelativeLayout
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent" >
+        <TextView
+          android:id="@+id/text"
+		  android:text="@string/lunar_layout_text_text"
+		  android:visibility="visible"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:layout_centerInParent="true"
+          android:gravity="center_horizontal"
+          android:textColor="#88ffffff"
+          android:textSize="24sp"/>
+     </RelativeLayout>
+         
+</FrameLayout>
Index: res/layout/main.xml
===================================================================
--- res/layout/main.xml	(revision 1a91f0dcdf2b97c5a72bf534516f7a8af58cbdb2)
+++ res/layout/main.xml	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
@@ -1,12 +1,26 @@
 <?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="fill_parent"
-    android:layout_height="fill_parent"
-    >
-<TextView  
-    android:layout_width="fill_parent" 
-    android:layout_height="wrap_content" 
-    android:text="@string/hello"
-    />
-</LinearLayout>
+    android:layout_height="fill_parent">
+    
+    <com.example.helloandroid.GameView
+      android:id="@+id/lunar"
+      android:layout_width="fill_parent"
+      android:layout_height="fill_parent"/>
+    
+    <RelativeLayout
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent" >
+        <TextView
+          android:id="@+id/text"
+		  android:text="@string/lunar_layout_text_text"
+		  android:visibility="visible"
+          android:layout_width="wrap_content"
+          android:layout_height="wrap_content"
+          android:layout_centerInParent="true"
+          android:gravity="center_horizontal"
+          android:textColor="#88ffffff"
+          android:textSize="24sp"/>
+     </RelativeLayout>
+         
+</FrameLayout>
Index: res/values/strings.xml
===================================================================
--- res/values/strings.xml	(revision 1a91f0dcdf2b97c5a72bf534516f7a8af58cbdb2)
+++ res/values/strings.xml	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
@@ -1,5 +1,28 @@
 <?xml version="1.0" encoding="utf-8"?>
+
 <resources>
+	<string name="app_name">Lunar Lander</string>
+	
+    <string name="menu_start">Start</string>
+    <string name="menu_stop">Stop</string>
+    <string name="menu_pause">Pause</string>
+    <string name="menu_resume">Resume</string>
+    <string name="menu_easy">Easy</string>
+    <string name="menu_medium">Medium</string>
+    <string name="menu_hard">Hard</string>
+    
+    <string name="mode_ready">Lunar Lander\nPress Up To Play</string>
+	<string name="mode_pause">Paused\nPress Up To Resume</string>
+	<string name="mode_lose">Game Over\nPress Up To Play</string>
+	<string name="mode_win_prefix">Success!\n</string> 
+	<string name="mode_win_suffix">in a row\nPress Up to Play</string> 
+	
+	<string name="message_stopped">Stopped</string>
+	<string name="message_off_pad">Off Landing Pad</string> 
+	<string name="message_too_fast">Too Fast</string> 
+	<string name="message_bad_angle">Bad Angle</string> 
+
+    <string name="lunar_layout_text_text"></string>
+    
     <string name="hello">Hello World, HelloAndroid!</string>
-    <string name="app_name">Hello, Android</string>
 </resources>
Index: src/com/example/helloandroid/Game.java
===================================================================
--- src/com/example/helloandroid/Game.java	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
+++ src/com/example/helloandroid/Game.java	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
@@ -0,0 +1,145 @@
+package com.example.helloandroid;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.Window;
+import android.widget.TextView;
+import com.example.helloandroid.GameView.DrawingThread;
+
+public class Game extends Activity {
+    private static final int MENU_EASY = 1;
+
+    private static final int MENU_HARD = 2;
+
+    private static final int MENU_MEDIUM = 3;
+
+    private static final int MENU_PAUSE = 4;
+
+    private static final int MENU_RESUME = 5;
+
+    private static final int MENU_START = 6;
+
+    private static final int MENU_STOP = 7;
+
+    /** A handle to the thread that's actually running the animation. */
+    private DrawingThread mThread;
+
+    /** A handle to the View in which the game is running. */
+    private GameView mGameView;
+
+    /**
+     * Invoked during init to give the Activity a chance to set up its Menu.
+     * 
+     * @param menu the Menu to which entries may be added
+     * @return true
+     */
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+
+        menu.add(0, MENU_START, 0, R.string.menu_start);
+        menu.add(0, MENU_STOP, 0, R.string.menu_stop);
+        menu.add(0, MENU_PAUSE, 0, R.string.menu_pause);
+        menu.add(0, MENU_RESUME, 0, R.string.menu_resume);
+        menu.add(0, MENU_EASY, 0, R.string.menu_easy);
+        menu.add(0, MENU_MEDIUM, 0, R.string.menu_medium);
+        menu.add(0, MENU_HARD, 0, R.string.menu_hard);
+
+        return true;
+    }
+
+    /**
+     * Invoked when the user selects an item from the Menu.
+     * 
+     * @param item the Menu entry which was selected
+     * @return true if the Menu item was legit (and we consumed it), false
+     *         otherwise
+     */
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case MENU_START:
+            	mThread.doStart();
+                return true;
+            case MENU_STOP:
+            	mThread.setState(DrawingThread.STATE_LOSE,
+                        getText(R.string.message_stopped));
+                return true;
+            case MENU_PAUSE:
+            	mThread.pause();
+                return true;
+            case MENU_RESUME:
+            	mThread.unpause();
+                return true;
+            case MENU_EASY:
+            	mThread.setDifficulty(DrawingThread.DIFFICULTY_EASY);
+                return true;
+            case MENU_MEDIUM:
+            	mThread.setDifficulty(DrawingThread.DIFFICULTY_MEDIUM);
+                return true;
+            case MENU_HARD:
+            	mThread.setDifficulty(DrawingThread.DIFFICULTY_HARD);
+                return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Invoked when the Activity is created.
+     * 
+     * @param savedInstanceState a Bundle containing state saved from a previous
+     *        execution, or null if this is a new execution
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // turn off the window's title bar
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+        // tell system to use the layout defined in our XML file
+        setContentView(R.layout.main);
+
+        mGameView = (GameView) findViewById(R.id.lunar);
+        mThread = mGameView.getThread();
+
+        mGameView.setTextView((TextView) findViewById(R.id.text));
+
+        if (savedInstanceState == null) {
+            // we were just launched: set up a new game
+        	mThread.setState(DrawingThread.STATE_READY);
+            Log.w(this.getClass().getName(), "SIS is null");
+        } else {
+            // we are being restored: resume a previous game
+        	mThread.restoreState(savedInstanceState);
+            Log.w(this.getClass().getName(), "SIS is nonnull");
+        }
+    }
+
+    /**
+     * Invoked when the Activity loses user focus.
+     */
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mGameView.getThread().pause(); // pause game when Activity pauses
+    }
+
+    /**
+     * Notification that something is about to happen, to give the Activity a
+     * chance to save state.
+     * 
+     * @param outState a Bundle into which this Activity should save its state
+     */
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        // just have the View's thread save its state into our Bundle
+        super.onSaveInstanceState(outState);
+        mThread.saveState(outState);
+        Log.w(this.getClass().getName(), "SIS called");
+    }
+}
Index: src/com/example/helloandroid/GameView.java
===================================================================
--- src/com/example/helloandroid/GameView.java	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
+++ src/com/example/helloandroid/GameView.java	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
@@ -0,0 +1,814 @@
+package com.example.helloandroid;
+
+import java.util.ArrayList;
+import java.util.Random;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.widget.TextView;
+
+/**
+ * View that draws, takes keystrokes, etc. for a simple LunarLander game.
+ * 
+ * Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the
+ * current ship physics. All x/y etc. are measured with (0,0) at the lower left.
+ * updatePhysics() advances the physics based on realtime. draw() renders the
+ * ship, and does an invalidate() to prompt another draw() as soon as possible
+ * by the system.
+ */
+class GameView extends SurfaceView implements SurfaceHolder.Callback {
+    class DrawingThread extends Thread {
+        /*
+         * Difficulty setting constants
+         */
+        public static final int DIFFICULTY_EASY = 0;
+        public static final int DIFFICULTY_HARD = 1;
+        public static final int DIFFICULTY_MEDIUM = 2;
+        /*
+         * Physics constants
+         */
+        public static final int PHYS_DOWN_ACCEL_SEC = 35;
+        public static final int PHYS_FIRE_ACCEL_SEC = 80;
+        public static final int PHYS_FUEL_INIT = 60;
+        public static final int PHYS_FUEL_MAX = 100;
+        public static final int PHYS_FUEL_SEC = 10;
+        public static final int PHYS_SLEW_SEC = 120; // degrees/second rotate
+        public static final int PHYS_SPEED_HYPERSPACE = 180;
+        public static final int PHYS_SPEED_INIT = 30;
+        public static final int PHYS_SPEED_MAX = 120;
+        /*
+         * State-tracking constants
+         */
+        public static final int STATE_LOSE = 1;
+        public static final int STATE_PAUSE = 2;
+        public static final int STATE_READY = 3;
+        public static final int STATE_RUNNING = 4;
+        public static final int STATE_WIN = 5;
+
+        /*
+         * Goal condition constants
+         */
+        public static final int TARGET_ANGLE = 18; // > this angle means crash
+        public static final int TARGET_BOTTOM_PADDING = 17; // px below gear
+        public static final int TARGET_PAD_HEIGHT = 8; // how high above ground
+        public static final int TARGET_SPEED = 28; // > this speed means crash
+        public static final double TARGET_WIDTH = 1.6; // width of target
+        /*
+         * UI constants (i.e. the speed & fuel bars)
+         */
+        public static final int UI_BAR = 100; // width of the bar(s)
+        public static final int UI_BAR_HEIGHT = 10; // height of the bar(s)
+        private static final String KEY_DIFFICULTY = "mDifficulty";
+        private static final String KEY_DX = "mDX";
+
+        private static final String KEY_DY = "mDY";
+        private static final String KEY_FUEL = "mFuel";
+        private static final String KEY_GOAL_ANGLE = "mGoalAngle";
+        private static final String KEY_GOAL_SPEED = "mGoalSpeed";
+        private static final String KEY_GOAL_WIDTH = "mGoalWidth";
+
+        private static final String KEY_GOAL_X = "mGoalX";
+        private static final String KEY_HEADING = "mHeading";
+        private static final String KEY_LANDER_HEIGHT = "mLanderHeight";
+        private static final String KEY_LANDER_WIDTH = "mLanderWidth";
+        private static final String KEY_WINS = "mWinsInARow";
+
+        private static final String KEY_X = "mX";
+        private static final String KEY_Y = "mY";
+
+        /*
+         * Member (state) fields
+         */
+        /** The drawable to use as the background of the animation canvas */
+        private Bitmap mBackgroundImage;
+
+        private int mCanvasHeight = 1;
+        private int mCanvasWidth = 1;
+
+        /** Default is MEDIUM. */
+        private int mDifficulty;
+
+        /** Velocity */
+        private double mDX;
+        private double mDY;
+
+        /** Is the engine burning? */
+        private boolean mEngineFiring;
+
+        /** Fuel remaining */
+        private double mFuel;
+
+        /** Allowed angle. */
+        private int mGoalAngle;
+
+        /** Allowed speed. */
+        private int mGoalSpeed;
+
+        /** Width of the landing pad. */
+        private int mGoalWidth;
+
+        /** X of the landing pad. */
+        private int mGoalX;
+
+        /** Message handler used by thread to interact with TextView */
+        private Handler mHandler;
+
+        /**
+         * Lander heading in degrees, with 0 up, 90 right. Kept in the range
+         * 0..360.
+         */
+        private double mHeading;
+
+        /** Pixel height of lander image. */
+        private int mLanderHeight;
+
+        /** What to draw for the Lander in its normal state */
+        private Drawable mLanderImage;
+
+        /** Pixel width of lander image. */
+        private int mLanderWidth;
+
+        /** Used to figure out elapsed time between frames */
+        private long mLastTime;
+
+        /** Paint to draw the lines on screen. */
+        private Paint mLinePaint, mTextPaint;
+
+        /** "Bad" speed-too-high variant of the line color. */
+        private Paint mLinePaintBad;
+
+        /** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */
+        private int mMode;
+
+        /** Currently rotating, -1 left, 0 none, 1 right. */
+        private int mRotating;
+
+        /** Indicate whether the surface has been created & is ready to draw */
+        private boolean mRun = false;
+
+        /** Handle to the surface manager object we interact with */
+        private SurfaceHolder mSurfaceHolder;
+
+        /** Number of wins in a row. */
+        private int mWinsInARow;
+
+        /** lander center. */
+        private double mX;
+        private double mY;
+        
+        private ArrayList<Planet> planets;
+
+        public DrawingThread(SurfaceHolder surfaceHolder, Context context,
+                Handler handler) {
+            // get handles to some important objects
+            mSurfaceHolder = surfaceHolder;
+            mHandler = handler;
+            mContext = context;
+
+            Resources res = context.getResources();
+            // cache handles to our key sprites & other drawables
+            mLanderImage = context.getResources().getDrawable(R.drawable.lander_plain);
+
+            // load background image as a Bitmap instead of a Drawable b/c
+            // we don't need to transform it and it's faster to draw this way
+            mBackgroundImage = BitmapFactory.decodeResource(res, R.drawable.earthrise);
+
+            // Use the regular lander image as the model size for all sprites
+            mLanderWidth = mLanderImage.getIntrinsicWidth();
+            mLanderHeight = mLanderImage.getIntrinsicHeight();
+
+            // Initialize paints for speedometer
+            mLinePaint = new Paint();
+            mLinePaint.setAntiAlias(true);
+            mLinePaint.setARGB(255, 0, 255, 0);
+
+            mLinePaintBad = new Paint();
+            mLinePaintBad.setAntiAlias(true);
+            mLinePaintBad.setARGB(255, 120, 180, 0);
+            
+            mTextPaint = new Paint();
+            mTextPaint.setAntiAlias(true);
+            mTextPaint.setARGB(255, 255, 0, 0);
+
+            mWinsInARow = 0;
+            mDifficulty = DIFFICULTY_MEDIUM;
+
+            // initial show-up of lander (not yet playing)
+            mX = mLanderWidth;
+            mY = mLanderHeight * 2;
+            mFuel = PHYS_FUEL_INIT;
+            mDX = 0;
+            mDY = 0;
+            mHeading = 0;
+            mEngineFiring = true;
+            
+            planets = new ArrayList<Planet>();
+        }
+
+        /**
+         * Starts the game, setting parameters for the current difficulty.
+         */
+        public void doStart() {
+            synchronized (mSurfaceHolder) {
+                // First set the game for Medium difficulty
+                mFuel = PHYS_FUEL_INIT;
+                mEngineFiring = false;
+                mGoalWidth = (int) (mLanderWidth * TARGET_WIDTH);
+                mGoalSpeed = TARGET_SPEED;
+                mGoalAngle = TARGET_ANGLE;
+                int speedInit = PHYS_SPEED_INIT;
+
+                // Adjust difficulty params for EASY/HARD
+                if (mDifficulty == DIFFICULTY_EASY) {
+                    mFuel = mFuel * 3 / 2;
+                    mGoalWidth = mGoalWidth * 4 / 3;
+                    mGoalSpeed = mGoalSpeed * 3 / 2;
+                    mGoalAngle = mGoalAngle * 4 / 3;
+                    speedInit = speedInit * 3 / 4;
+                } else if (mDifficulty == DIFFICULTY_HARD) {
+                    mFuel = mFuel * 7 / 8;
+                    mGoalWidth = mGoalWidth * 3 / 4;
+                    mGoalSpeed = mGoalSpeed * 7 / 8;
+                    speedInit = speedInit * 4 / 3;
+                }
+
+                // pick a convenient initial location for the lander sprite
+                mX = mCanvasWidth / 2;
+                mY = mCanvasHeight - mLanderHeight / 2;
+
+                // start with a little random motion
+                mDY = Math.random() * -speedInit;
+                mDX = Math.random() * 2 * speedInit - speedInit;
+                mHeading = 0;
+
+                // Figure initial spot for landing, not too near center
+                while (true) {
+                    mGoalX = (int) (Math.random() * (mCanvasWidth - mGoalWidth));
+                    if (Math.abs(mGoalX - (mX - mLanderWidth / 2)) > mCanvasHeight / 6)
+                        break;
+                }
+
+                mLastTime = System.currentTimeMillis() + 100;
+                setState(STATE_RUNNING);
+            }
+        }
+
+        /**
+         * Pauses the physics update & animation.
+         */
+        public void pause() {
+            synchronized (mSurfaceHolder) {
+                if (mMode == STATE_RUNNING) setState(STATE_PAUSE);
+            }
+        }
+
+        /**
+         * Restores game state from the indicated Bundle. Typically called when
+         * the Activity is being restored after having been previously
+         * destroyed.
+         * 
+         * @param savedState Bundle containing the game state
+         */
+        public synchronized void restoreState(Bundle savedState) {
+            synchronized (mSurfaceHolder) {
+                setState(STATE_PAUSE);
+                mRotating = 0;
+                mEngineFiring = false;
+
+                mDifficulty = savedState.getInt(KEY_DIFFICULTY);
+                mX = savedState.getDouble(KEY_X);
+                mY = savedState.getDouble(KEY_Y);
+                mDX = savedState.getDouble(KEY_DX);
+                mDY = savedState.getDouble(KEY_DY);
+                mHeading = savedState.getDouble(KEY_HEADING);
+
+                mLanderWidth = savedState.getInt(KEY_LANDER_WIDTH);
+                mLanderHeight = savedState.getInt(KEY_LANDER_HEIGHT);
+                mGoalX = savedState.getInt(KEY_GOAL_X);
+                mGoalSpeed = savedState.getInt(KEY_GOAL_SPEED);
+                mGoalAngle = savedState.getInt(KEY_GOAL_ANGLE);
+                mGoalWidth = savedState.getInt(KEY_GOAL_WIDTH);
+                mWinsInARow = savedState.getInt(KEY_WINS);
+                mFuel = savedState.getDouble(KEY_FUEL);
+            }
+        }
+
+        @Override
+        public void run() {
+            while (mRun) {
+                Canvas c = null;
+                try {
+                    c = mSurfaceHolder.lockCanvas(null);
+                    synchronized (mSurfaceHolder) {
+                        if (mMode == STATE_RUNNING) updatePhysics();
+                        doDraw(c);
+                    }
+                } finally {
+                    // do this in a finally so that if an exception is thrown
+                    // during the above, we don't leave the Surface in an
+                    // inconsistent state
+                    if (c != null) {
+                        mSurfaceHolder.unlockCanvasAndPost(c);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Dump game state to the provided Bundle. Typically called when the
+         * Activity is being suspended.
+         * 
+         * @return Bundle with this view's state
+         */
+        public Bundle saveState(Bundle map) {
+            synchronized (mSurfaceHolder) {
+                if (map != null) {
+                    map.putInt(KEY_DIFFICULTY, Integer.valueOf(mDifficulty));
+                    map.putDouble(KEY_X, Double.valueOf(mX));
+                    map.putDouble(KEY_Y, Double.valueOf(mY));
+                    map.putDouble(KEY_DX, Double.valueOf(mDX));
+                    map.putDouble(KEY_DY, Double.valueOf(mDY));
+                    map.putDouble(KEY_HEADING, Double.valueOf(mHeading));
+                    map.putInt(KEY_LANDER_WIDTH, Integer.valueOf(mLanderWidth));
+                    map.putInt(KEY_LANDER_HEIGHT, Integer
+                            .valueOf(mLanderHeight));
+                    map.putInt(KEY_GOAL_X, Integer.valueOf(mGoalX));
+                    map.putInt(KEY_GOAL_SPEED, Integer.valueOf(mGoalSpeed));
+                    map.putInt(KEY_GOAL_ANGLE, Integer.valueOf(mGoalAngle));
+                    map.putInt(KEY_GOAL_WIDTH, Integer.valueOf(mGoalWidth));
+                    map.putInt(KEY_WINS, Integer.valueOf(mWinsInARow));
+                    map.putDouble(KEY_FUEL, Double.valueOf(mFuel));
+                }
+            }
+            return map;
+        }
+
+        /**
+         * Sets the current difficulty.
+         * 
+         * @param difficulty
+         */
+        public void setDifficulty(int difficulty) {
+            synchronized (mSurfaceHolder) {
+                mDifficulty = difficulty;
+            }
+        }
+
+        /**
+         * Sets if the engine is currently firing.
+         */
+        public void setFiring(boolean firing) {
+            synchronized (mSurfaceHolder) {
+                mEngineFiring = firing;
+            }
+        }
+
+        /**
+         * Used to signal the thread whether it should be running or not.
+         * Passing true allows the thread to run; passing false will shut it
+         * down if it's already running. Calling start() after this was most
+         * recently called with false will result in an immediate shutdown.
+         * 
+         * @param b true to run, false to shut down
+         */
+        public void setRunning(boolean b) {
+            mRun = b;
+        }
+
+        /**
+         * Sets the game mode. That is, whether we are running, paused, in the
+         * failure state, in the victory state, etc.
+         * 
+         * @see #setState(int, CharSequence)
+         * @param mode one of the STATE_* constants
+         */
+        public void setState(int mode) {
+            synchronized (mSurfaceHolder) {
+                setState(mode, null);
+            }
+        }
+
+        /**
+         * Sets the game mode. That is, whether we are running, paused, in the
+         * failure state, in the victory state, etc.
+         * 
+         * @param mode one of the STATE_* constants
+         * @param message string to add to screen or null
+         */
+        public void setState(int mode, CharSequence message) {
+            /*
+             * This method optionally can cause a text message to be displayed
+             * to the user when the mode changes. Since the View that actually
+             * renders that text is part of the main View hierarchy and not
+             * owned by this thread, we can't touch the state of that View.
+             * Instead we use a Message + Handler to relay commands to the main
+             * thread, which updates the user-text View.
+             */
+        	boolean test = true;
+        	if(test)
+        		return;
+            synchronized (mSurfaceHolder) {
+                mMode = mode;
+
+                if (mMode == STATE_RUNNING) {
+                    Message msg = mHandler.obtainMessage();
+                    Bundle b = new Bundle();
+                    b.putString("text", "");
+                    b.putInt("viz", GameView.INVISIBLE);
+                    msg.setData(b);
+                    mHandler.sendMessage(msg);
+                } else {
+                    mRotating = 0;
+                    mEngineFiring = false;
+                    Resources res = mContext.getResources();
+                    CharSequence str = "";
+                    if (mMode == STATE_READY)
+                        str = res.getText(R.string.mode_ready);
+                    else if (mMode == STATE_PAUSE)
+                        str = res.getText(R.string.mode_pause);
+                    else if (mMode == STATE_LOSE)
+                        str = res.getText(R.string.mode_lose);
+                    else if (mMode == STATE_WIN)
+                        str = res.getString(R.string.mode_win_prefix)
+                                + mWinsInARow + " "
+                                + res.getString(R.string.mode_win_suffix);
+
+                    if (message != null) {
+                        str = message + "\n" + str;
+                    }
+
+                    if (mMode == STATE_LOSE) mWinsInARow = 0;
+
+                    Message msg = mHandler.obtainMessage();
+                    Bundle b = new Bundle();
+                    b.putString("text", str.toString());
+                    b.putInt("viz", GameView.VISIBLE);
+                    msg.setData(b);
+                    mHandler.sendMessage(msg);
+                }
+            }
+        }
+
+        /* Callback invoked when the surface dimensions change. */
+        public void setSurfaceSize(int width, int height) {
+            // synchronized to make sure these all change atomically
+            synchronized (mSurfaceHolder) {
+                mCanvasWidth = width;
+                mCanvasHeight = height;
+                
+                Log.i(this.getClass().getName(), "width: "+mCanvasWidth+", height: "+mCanvasHeight);
+                
+                Random rand = new Random();
+
+                for(int x=0; x<10; x++) {
+                	Planet p = new Planet(rand.nextInt(45)+5, rand.nextInt(mCanvasWidth), rand.nextInt(mCanvasHeight));
+                	p.setNumShips(rand.nextInt(150));
+                	
+                	if(Planet.collisionDetected(p, planets)) {
+                		x--;
+                	}else if(p.getX()-p.getRadius() < 0 || mCanvasWidth<=p.getX()+p.getRadius() ||
+                			 p.getY()-p.getRadius() < 0 || mCanvasHeight<=p.getY()+p.getRadius()) {
+                		x--;
+                	}else {
+                		planets.add(p);
+                	}
+                }
+                	
+                // don't forget to resize the background image
+                mBackgroundImage = Bitmap.createScaledBitmap(mBackgroundImage, width, height, true);
+            }
+        }
+
+        /**
+         * Resumes from a pause.
+         */
+        public void unpause() {
+            // Move the real time clock up to now
+            synchronized (mSurfaceHolder) {
+                mLastTime = System.currentTimeMillis() + 100;
+            }
+            setState(STATE_RUNNING);
+        }
+
+        /**
+         * Handles a key-down event.
+         * 
+         * @param keyCode the key that was pressed
+         * @param msg the original event object
+         * @return true
+         */
+        boolean doKeyDown(int keyCode, KeyEvent msg) {
+            synchronized (mSurfaceHolder) {
+                boolean okStart = false;
+                if (keyCode == KeyEvent.KEYCODE_DPAD_UP) okStart = true;
+                if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) okStart = true;
+                if (keyCode == KeyEvent.KEYCODE_S) okStart = true;
+
+                if (okStart
+                        && (mMode == STATE_READY || mMode == STATE_LOSE || mMode == STATE_WIN)) {
+                    // ready-to-start -> start
+                    doStart();
+                    return true;
+                } else if (mMode == STATE_PAUSE && okStart) {
+                    // paused -> running
+                    unpause();
+                    return true;
+                } else if (mMode == STATE_RUNNING) {
+                    return true;
+                }
+
+                return false;
+            }
+        }
+
+        /**
+         * Handles a key-up event.
+         * 
+         * @param keyCode the key that was pressed
+         * @param msg the original event object
+         * @return true if the key was handled and consumed, or else false
+         */
+        boolean doKeyUp(int keyCode, KeyEvent msg) {
+            boolean handled = false;
+
+            synchronized (mSurfaceHolder) {
+                if (mMode == STATE_RUNNING) {
+                    if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+                            || keyCode == KeyEvent.KEYCODE_SPACE) {
+                        handled = true;
+                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+                            || keyCode == KeyEvent.KEYCODE_Q
+                            || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+                            || keyCode == KeyEvent.KEYCODE_W) {
+                        handled = true;
+                    }
+                }
+            }
+
+            return handled;
+        }
+
+        /**
+         * Draws the ship, fuel/speed bars, and background to the provided
+         * Canvas.
+         */
+        private void doDraw(Canvas canvas) {
+            // Draw the background image. Operations on the Canvas accumulate
+            // so this is like clearing the screen.
+            canvas.drawBitmap(mBackgroundImage, 0, 0, null);
+            
+            for(Planet p : planets) {
+            	canvas.drawCircle(p.getX(), p.getY(), p.getRadius(), mLinePaint);
+            	canvas.drawText(Integer.toString(p.getNumShips()), p.getX(), p.getY(), mTextPaint);
+            }
+            
+            int yTop = mCanvasHeight - ((int) mY + mLanderHeight / 2);
+            int xLeft = (int) mX - mLanderWidth / 2;
+
+            // Draw the ship with its current rotation
+            canvas.save();
+            canvas.rotate((float)mHeading, (float)mX, mCanvasHeight - (float)mY);
+            if (mMode == STATE_LOSE) {
+                //mCrashedImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop + mLanderHeight);
+                //mCrashedImage.draw(canvas);
+            } else if (mEngineFiring) {
+                //mFiringImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop + mLanderHeight);
+                //mFiringImage.draw(canvas);
+            } else {
+                mLanderImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop + mLanderHeight);
+                mLanderImage.draw(canvas);
+            }
+            canvas.restore();
+        }
+
+        /**
+         * Figures the lander state (x, y, fuel, ...) based on the passage of
+         * realtime. Does not invalidate(). Called at the start of draw().
+         * Detects the end-of-game and sets the UI to the next state.
+         */
+        private void updatePhysics() {
+            long now = System.currentTimeMillis();
+
+            // Do nothing if mLastTime is in the future.
+            // This allows the game-start to delay the start of the physics
+            // by 100ms or whatever.
+            if (mLastTime > now) return;
+
+            double elapsed = (now - mLastTime) / 1000.0;
+
+            // mRotating -- update heading
+            if (mRotating != 0) {
+                mHeading += mRotating * (PHYS_SLEW_SEC * elapsed);
+
+                // Bring things back into the range 0..360
+                if (mHeading < 0)
+                    mHeading += 360;
+                else if (mHeading >= 360) mHeading -= 360;
+            }
+
+            // Base accelerations -- 0 for x, gravity for y
+            double ddx = 0.0;
+            double ddy = -PHYS_DOWN_ACCEL_SEC * elapsed;
+
+            if (mEngineFiring) {
+                // taking 0 as up, 90 as to the right
+                // cos(deg) is ddy component, sin(deg) is ddx component
+                double elapsedFiring = elapsed;
+                double fuelUsed = elapsedFiring * PHYS_FUEL_SEC;
+
+                // tricky case where we run out of fuel partway through the
+                // elapsed
+                if (fuelUsed > mFuel) {
+                    elapsedFiring = mFuel / fuelUsed * elapsed;
+                    fuelUsed = mFuel;
+
+                    // Oddball case where we adjust the "control" from here
+                    mEngineFiring = false;
+                }
+
+                mFuel -= fuelUsed;
+
+                // have this much acceleration from the engine
+                double accel = PHYS_FIRE_ACCEL_SEC * elapsedFiring;
+
+                double radians = 2 * Math.PI * mHeading / 360;
+                ddx = Math.sin(radians) * accel;
+                ddy += Math.cos(radians) * accel;
+            }
+
+            for(Planet p : planets) {
+            	p.update();
+            }
+            
+            double dxOld = mDX;
+            double dyOld = mDY;
+
+            // figure speeds for the end of the period
+            mDX += ddx;
+            mDY += ddy;
+
+            // figure position based on average speed during the period
+            mX += elapsed * (mDX + dxOld) / 2;
+            mY += elapsed * (mDY + dyOld) / 2;
+
+            mLastTime = now;
+
+            // Evaluate if we have landed ... stop the game
+            double yLowerBound = TARGET_PAD_HEIGHT + mLanderHeight / 2
+                    - TARGET_BOTTOM_PADDING;
+            if (mY <= yLowerBound) {
+                mY = yLowerBound;
+
+                int result = STATE_LOSE;
+                CharSequence message = "";
+                Resources res = mContext.getResources();
+                double speed = Math.sqrt(mDX * mDX + mDY * mDY);
+                boolean onGoal = (mGoalX <= mX - mLanderWidth / 2 && mX
+                        + mLanderWidth / 2 <= mGoalX + mGoalWidth);
+
+                // "Hyperspace" win -- upside down, going fast,
+                // puts you back at the top.
+                if (onGoal && Math.abs(mHeading - 180) < mGoalAngle
+                        && speed > PHYS_SPEED_HYPERSPACE) {
+                    result = STATE_WIN;
+                    mWinsInARow++;
+                    doStart();
+
+                    return;
+                    // Oddball case: this case does a return, all other cases
+                    // fall through to setMode() below.
+                } else if (!onGoal) {
+                    message = res.getText(R.string.message_off_pad);
+                } else if (!(mHeading <= mGoalAngle || mHeading >= 360 - mGoalAngle)) {
+                    message = res.getText(R.string.message_bad_angle);
+                } else if (speed > mGoalSpeed) {
+                    message = res.getText(R.string.message_too_fast);
+                } else {
+                    result = STATE_WIN;
+                    mWinsInARow++;
+                }
+
+                setState(result, message);
+            }
+        }
+    }
+
+    /** Handle to the application context, used to e.g. fetch Drawables. */
+    private Context mContext;
+
+    /** Pointer to the text view to display "Paused.." etc. */
+    private TextView mStatusText;
+
+    /** The thread that actually draws the animation */
+    private DrawingThread thread;
+
+    public GameView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // register our interest in hearing about changes to our surface
+        SurfaceHolder holder = getHolder();
+        holder.addCallback(this);
+
+        // create thread only; it's started in surfaceCreated()
+        thread = new DrawingThread(holder, context, new Handler() {
+            @Override
+            public void handleMessage(Message m) {
+                mStatusText.setVisibility(m.getData().getInt("viz"));
+                mStatusText.setText(m.getData().getString("text"));
+            }
+        });
+
+        setFocusable(true); // make sure we get key events
+    }
+
+    /**
+     * Fetches the animation thread corresponding to this LunarView.
+     * 
+     * @return the animation thread
+     */
+    public DrawingThread getThread() {
+        return thread;
+    }
+
+    /**
+     * Standard override to get key-press events.
+     */
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent msg) {
+        return thread.doKeyDown(keyCode, msg);
+    }
+
+    /**
+     * Standard override for key-up. We actually care about these, so we can
+     * turn off the engine or stop rotating.
+     */
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent msg) {
+        return thread.doKeyUp(keyCode, msg);
+    }
+
+    /**
+     * Standard window-focus override. Notice focus lost so we can pause on
+     * focus lost. e.g. user switches to take a call.
+     */
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        if (!hasWindowFocus) thread.pause();
+    }
+
+    /**
+     * Installs a pointer to the text view used for messages.
+     */
+    public void setTextView(TextView textView) {
+        mStatusText = textView;
+    }
+
+    /* Callback invoked when the surface dimensions change. */
+    public void surfaceChanged(SurfaceHolder holder, int format, int width,
+            int height) {
+        thread.setSurfaceSize(width, height);
+    }
+
+    /*
+     * Callback invoked when the Surface has been created and is ready to be
+     * used.
+     */
+    public void surfaceCreated(SurfaceHolder holder) {
+        // start the thread here so that we don't busy-wait in run()
+        // waiting for the surface to be created
+        thread.setRunning(true);
+        thread.start();
+    }
+
+    /*
+     * Callback invoked when the Surface has been destroyed and must no longer
+     * be touched. WARNING: after this method returns, the Surface/Canvas must
+     * never be touched again!
+     */
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        // we have to tell thread to shut down & wait for it to finish, or else
+        // it might touch the Surface after we return and explode
+        boolean retry = true;
+        thread.setRunning(false);
+        while (retry) {
+            try {
+                thread.join();
+                retry = false;
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+}
Index: c/com/example/helloandroid/HelloAndroid.java
===================================================================
--- src/com/example/helloandroid/HelloAndroid.java	(revision 1a91f0dcdf2b97c5a72bf534516f7a8af58cbdb2)
+++ 	(revision )
@@ -1,13 +1,0 @@
-package com.example.helloandroid;
-
-import android.app.Activity;
-import android.os.Bundle;
-
-public class HelloAndroid extends Activity {
-    /** Called when the activity is first created. */
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.main);
-    }
-}
Index: src/com/example/helloandroid/LunarLander.java
===================================================================
--- src/com/example/helloandroid/LunarLander.java	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
+++ src/com/example/helloandroid/LunarLander.java	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
@@ -0,0 +1,157 @@
+package com.example.helloandroid;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.Window;
+import android.widget.TextView;
+
+import com.example.helloandroid.LunarView.LunarThread;
+
+/**
+ * This is a simple LunarLander activity that houses a single LunarView. It
+ * demonstrates...
+ * <ul>
+ * <li>animating by calling invalidate() from draw()
+ * <li>loading and drawing resources
+ * <li>handling onPause() in an animation
+ * </ul>
+ */
+public class LunarLander extends Activity {
+    private static final int MENU_EASY = 1;
+
+    private static final int MENU_HARD = 2;
+
+    private static final int MENU_MEDIUM = 3;
+
+    private static final int MENU_PAUSE = 4;
+
+    private static final int MENU_RESUME = 5;
+
+    private static final int MENU_START = 6;
+
+    private static final int MENU_STOP = 7;
+
+    /** A handle to the thread that's actually running the animation. */
+    private LunarThread mLunarThread;
+
+    /** A handle to the View in which the game is running. */
+    private LunarView mLunarView;
+
+    /**
+     * Invoked during init to give the Activity a chance to set up its Menu.
+     * 
+     * @param menu the Menu to which entries may be added
+     * @return true
+     */
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+
+        menu.add(0, MENU_START, 0, R.string.menu_start);
+        menu.add(0, MENU_STOP, 0, R.string.menu_stop);
+        menu.add(0, MENU_PAUSE, 0, R.string.menu_pause);
+        menu.add(0, MENU_RESUME, 0, R.string.menu_resume);
+        menu.add(0, MENU_EASY, 0, R.string.menu_easy);
+        menu.add(0, MENU_MEDIUM, 0, R.string.menu_medium);
+        menu.add(0, MENU_HARD, 0, R.string.menu_hard);
+
+        return true;
+    }
+
+    /**
+     * Invoked when the user selects an item from the Menu.
+     * 
+     * @param item the Menu entry which was selected
+     * @return true if the Menu item was legit (and we consumed it), false
+     *         otherwise
+     */
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case MENU_START:
+                mLunarThread.doStart();
+                return true;
+            case MENU_STOP:
+                mLunarThread.setState(LunarThread.STATE_LOSE,
+                        getText(R.string.message_stopped));
+                return true;
+            case MENU_PAUSE:
+                mLunarThread.pause();
+                return true;
+            case MENU_RESUME:
+                mLunarThread.unpause();
+                return true;
+            case MENU_EASY:
+                mLunarThread.setDifficulty(LunarThread.DIFFICULTY_EASY);
+                return true;
+            case MENU_MEDIUM:
+                mLunarThread.setDifficulty(LunarThread.DIFFICULTY_MEDIUM);
+                return true;
+            case MENU_HARD:
+                mLunarThread.setDifficulty(LunarThread.DIFFICULTY_HARD);
+                return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Invoked when the Activity is created.
+     * 
+     * @param savedInstanceState a Bundle containing state saved from a previous
+     *        execution, or null if this is a new execution
+     */
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // turn off the window's title bar
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+        // tell system to use the layout defined in our XML file
+        setContentView(R.layout.lunar_layout);
+
+        // get handles to the LunarView from XML, and its LunarThread
+        mLunarView = (LunarView) findViewById(R.id.lunar);
+        mLunarThread = mLunarView.getThread();
+
+        // give the LunarView a handle to the TextView used for messages
+        mLunarView.setTextView((TextView) findViewById(R.id.text));
+
+        if (savedInstanceState == null) {
+            // we were just launched: set up a new game
+            mLunarThread.setState(LunarThread.STATE_READY);
+            Log.w(this.getClass().getName(), "SIS is null");
+        } else {
+            // we are being restored: resume a previous game
+            mLunarThread.restoreState(savedInstanceState);
+            Log.w(this.getClass().getName(), "SIS is nonnull");
+        }
+    }
+
+    /**
+     * Invoked when the Activity loses user focus.
+     */
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mLunarView.getThread().pause(); // pause game when Activity pauses
+    }
+
+    /**
+     * Notification that something is about to happen, to give the Activity a
+     * chance to save state.
+     * 
+     * @param outState a Bundle into which this Activity should save its state
+     */
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        // just have the View's thread save its state into our Bundle
+        super.onSaveInstanceState(outState);
+        mLunarThread.saveState(outState);
+        Log.w(this.getClass().getName(), "SIS called");
+    }
+}
Index: src/com/example/helloandroid/LunarView.java
===================================================================
--- src/com/example/helloandroid/LunarView.java	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
+++ src/com/example/helloandroid/LunarView.java	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
@@ -0,0 +1,866 @@
+package com.example.helloandroid;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.View;
+import android.widget.TextView;
+
+
+/**
+ * View that draws, takes keystrokes, etc. for a simple LunarLander game.
+ * 
+ * Has a mode which RUNNING, PAUSED, etc. Has a x, y, dx, dy, ... capturing the
+ * current ship physics. All x/y etc. are measured with (0,0) at the lower left.
+ * updatePhysics() advances the physics based on realtime. draw() renders the
+ * ship, and does an invalidate() to prompt another draw() as soon as possible
+ * by the system.
+ */
+class LunarView extends SurfaceView implements SurfaceHolder.Callback {
+    class LunarThread extends Thread {
+        /*
+         * Difficulty setting constants
+         */
+        public static final int DIFFICULTY_EASY = 0;
+        public static final int DIFFICULTY_HARD = 1;
+        public static final int DIFFICULTY_MEDIUM = 2;
+        /*
+         * Physics constants
+         */
+        public static final int PHYS_DOWN_ACCEL_SEC = 35;
+        public static final int PHYS_FIRE_ACCEL_SEC = 80;
+        public static final int PHYS_FUEL_INIT = 60;
+        public static final int PHYS_FUEL_MAX = 100;
+        public static final int PHYS_FUEL_SEC = 10;
+        public static final int PHYS_SLEW_SEC = 120; // degrees/second rotate
+        public static final int PHYS_SPEED_HYPERSPACE = 180;
+        public static final int PHYS_SPEED_INIT = 30;
+        public static final int PHYS_SPEED_MAX = 120;
+        /*
+         * State-tracking constants
+         */
+        public static final int STATE_LOSE = 1;
+        public static final int STATE_PAUSE = 2;
+        public static final int STATE_READY = 3;
+        public static final int STATE_RUNNING = 4;
+        public static final int STATE_WIN = 5;
+
+        /*
+         * Goal condition constants
+         */
+        public static final int TARGET_ANGLE = 18; // > this angle means crash
+        public static final int TARGET_BOTTOM_PADDING = 17; // px below gear
+        public static final int TARGET_PAD_HEIGHT = 8; // how high above ground
+        public static final int TARGET_SPEED = 28; // > this speed means crash
+        public static final double TARGET_WIDTH = 1.6; // width of target
+        /*
+         * UI constants (i.e. the speed & fuel bars)
+         */
+        public static final int UI_BAR = 100; // width of the bar(s)
+        public static final int UI_BAR_HEIGHT = 10; // height of the bar(s)
+        private static final String KEY_DIFFICULTY = "mDifficulty";
+        private static final String KEY_DX = "mDX";
+
+        private static final String KEY_DY = "mDY";
+        private static final String KEY_FUEL = "mFuel";
+        private static final String KEY_GOAL_ANGLE = "mGoalAngle";
+        private static final String KEY_GOAL_SPEED = "mGoalSpeed";
+        private static final String KEY_GOAL_WIDTH = "mGoalWidth";
+
+        private static final String KEY_GOAL_X = "mGoalX";
+        private static final String KEY_HEADING = "mHeading";
+        private static final String KEY_LANDER_HEIGHT = "mLanderHeight";
+        private static final String KEY_LANDER_WIDTH = "mLanderWidth";
+        private static final String KEY_WINS = "mWinsInARow";
+
+        private static final String KEY_X = "mX";
+        private static final String KEY_Y = "mY";
+
+        /*
+         * Member (state) fields
+         */
+        /** The drawable to use as the background of the animation canvas */
+        private Bitmap mBackgroundImage;
+
+        /**
+         * Current height of the surface/canvas.
+         * 
+         * @see #setSurfaceSize
+         */
+        private int mCanvasHeight = 1;
+
+        /**
+         * Current width of the surface/canvas.
+         * 
+         * @see #setSurfaceSize
+         */
+        private int mCanvasWidth = 1;
+
+        /** What to draw for the Lander when it has crashed */
+        private Drawable mCrashedImage;
+
+        /**
+         * Current difficulty -- amount of fuel, allowed angle, etc. Default is
+         * MEDIUM.
+         */
+        private int mDifficulty;
+
+        /** Velocity dx. */
+        private double mDX;
+
+        /** Velocity dy. */
+        private double mDY;
+
+        /** Is the engine burning? */
+        private boolean mEngineFiring;
+
+        /** What to draw for the Lander when the engine is firing */
+        private Drawable mFiringImage;
+
+        /** Fuel remaining */
+        private double mFuel;
+
+        /** Allowed angle. */
+        private int mGoalAngle;
+
+        /** Allowed speed. */
+        private int mGoalSpeed;
+
+        /** Width of the landing pad. */
+        private int mGoalWidth;
+
+        /** X of the landing pad. */
+        private int mGoalX;
+
+        /** Message handler used by thread to interact with TextView */
+        private Handler mHandler;
+
+        /**
+         * Lander heading in degrees, with 0 up, 90 right. Kept in the range
+         * 0..360.
+         */
+        private double mHeading;
+
+        /** Pixel height of lander image. */
+        private int mLanderHeight;
+
+        /** What to draw for the Lander in its normal state */
+        private Drawable mLanderImage;
+
+        /** Pixel width of lander image. */
+        private int mLanderWidth;
+
+        /** Used to figure out elapsed time between frames */
+        private long mLastTime;
+
+        /** Paint to draw the lines on screen. */
+        private Paint mLinePaint;
+
+        /** "Bad" speed-too-high variant of the line color. */
+        private Paint mLinePaintBad;
+
+        /** The state of the game. One of READY, RUNNING, PAUSE, LOSE, or WIN */
+        private int mMode;
+
+        /** Currently rotating, -1 left, 0 none, 1 right. */
+        private int mRotating;
+
+        /** Indicate whether the surface has been created & is ready to draw */
+        private boolean mRun = false;
+
+        /** Scratch rect object. */
+        private RectF mScratchRect;
+
+        /** Handle to the surface manager object we interact with */
+        private SurfaceHolder mSurfaceHolder;
+
+        /** Number of wins in a row. */
+        private int mWinsInARow;
+
+        /** X of lander center. */
+        private double mX;
+
+        /** Y of lander center. */
+        private double mY;
+
+        public LunarThread(SurfaceHolder surfaceHolder, Context context,
+                Handler handler) {
+            // get handles to some important objects
+            mSurfaceHolder = surfaceHolder;
+            mHandler = handler;
+            mContext = context;
+
+            Resources res = context.getResources();
+            // cache handles to our key sprites & other drawables
+            mLanderImage = context.getResources().getDrawable(
+                    R.drawable.lander_plain);
+            mFiringImage = context.getResources().getDrawable(
+                    R.drawable.lander_firing);
+            mCrashedImage = context.getResources().getDrawable(
+                    R.drawable.lander_crashed);
+
+            // load background image as a Bitmap instead of a Drawable b/c
+            // we don't need to transform it and it's faster to draw this way
+            mBackgroundImage = BitmapFactory.decodeResource(res,
+                    R.drawable.earthrise);
+
+            // Use the regular lander image as the model size for all sprites
+            mLanderWidth = mLanderImage.getIntrinsicWidth();
+            mLanderHeight = mLanderImage.getIntrinsicHeight();
+
+            // Initialize paints for speedometer
+            mLinePaint = new Paint();
+            mLinePaint.setAntiAlias(true);
+            mLinePaint.setARGB(255, 0, 255, 0);
+
+            mLinePaintBad = new Paint();
+            mLinePaintBad.setAntiAlias(true);
+            mLinePaintBad.setARGB(255, 120, 180, 0);
+
+            mScratchRect = new RectF(0, 0, 0, 0);
+
+            mWinsInARow = 0;
+            mDifficulty = DIFFICULTY_MEDIUM;
+
+            // initial show-up of lander (not yet playing)
+            mX = mLanderWidth;
+            mY = mLanderHeight * 2;
+            mFuel = PHYS_FUEL_INIT;
+            mDX = 0;
+            mDY = 0;
+            mHeading = 0;
+            mEngineFiring = true;
+        }
+
+        /**
+         * Starts the game, setting parameters for the current difficulty.
+         */
+        public void doStart() {
+            synchronized (mSurfaceHolder) {
+                // First set the game for Medium difficulty
+                mFuel = PHYS_FUEL_INIT;
+                mEngineFiring = false;
+                mGoalWidth = (int) (mLanderWidth * TARGET_WIDTH);
+                mGoalSpeed = TARGET_SPEED;
+                mGoalAngle = TARGET_ANGLE;
+                int speedInit = PHYS_SPEED_INIT;
+
+                // Adjust difficulty params for EASY/HARD
+                if (mDifficulty == DIFFICULTY_EASY) {
+                    mFuel = mFuel * 3 / 2;
+                    mGoalWidth = mGoalWidth * 4 / 3;
+                    mGoalSpeed = mGoalSpeed * 3 / 2;
+                    mGoalAngle = mGoalAngle * 4 / 3;
+                    speedInit = speedInit * 3 / 4;
+                } else if (mDifficulty == DIFFICULTY_HARD) {
+                    mFuel = mFuel * 7 / 8;
+                    mGoalWidth = mGoalWidth * 3 / 4;
+                    mGoalSpeed = mGoalSpeed * 7 / 8;
+                    speedInit = speedInit * 4 / 3;
+                }
+
+                // pick a convenient initial location for the lander sprite
+                mX = mCanvasWidth / 2;
+                mY = mCanvasHeight - mLanderHeight / 2;
+
+                // start with a little random motion
+                mDY = Math.random() * -speedInit;
+                mDX = Math.random() * 2 * speedInit - speedInit;
+                mHeading = 0;
+
+                // Figure initial spot for landing, not too near center
+                while (true) {
+                    mGoalX = (int) (Math.random() * (mCanvasWidth - mGoalWidth));
+                    if (Math.abs(mGoalX - (mX - mLanderWidth / 2)) > mCanvasHeight / 6)
+                        break;
+                }
+
+                mLastTime = System.currentTimeMillis() + 100;
+                setState(STATE_RUNNING);
+            }
+        }
+
+        /**
+         * Pauses the physics update & animation.
+         */
+        public void pause() {
+            synchronized (mSurfaceHolder) {
+                if (mMode == STATE_RUNNING) setState(STATE_PAUSE);
+            }
+        }
+
+        /**
+         * Restores game state from the indicated Bundle. Typically called when
+         * the Activity is being restored after having been previously
+         * destroyed.
+         * 
+         * @param savedState Bundle containing the game state
+         */
+        public synchronized void restoreState(Bundle savedState) {
+            synchronized (mSurfaceHolder) {
+                setState(STATE_PAUSE);
+                mRotating = 0;
+                mEngineFiring = false;
+
+                mDifficulty = savedState.getInt(KEY_DIFFICULTY);
+                mX = savedState.getDouble(KEY_X);
+                mY = savedState.getDouble(KEY_Y);
+                mDX = savedState.getDouble(KEY_DX);
+                mDY = savedState.getDouble(KEY_DY);
+                mHeading = savedState.getDouble(KEY_HEADING);
+
+                mLanderWidth = savedState.getInt(KEY_LANDER_WIDTH);
+                mLanderHeight = savedState.getInt(KEY_LANDER_HEIGHT);
+                mGoalX = savedState.getInt(KEY_GOAL_X);
+                mGoalSpeed = savedState.getInt(KEY_GOAL_SPEED);
+                mGoalAngle = savedState.getInt(KEY_GOAL_ANGLE);
+                mGoalWidth = savedState.getInt(KEY_GOAL_WIDTH);
+                mWinsInARow = savedState.getInt(KEY_WINS);
+                mFuel = savedState.getDouble(KEY_FUEL);
+            }
+        }
+
+        @Override
+        public void run() {
+            while (mRun) {
+                Canvas c = null;
+                try {
+                    c = mSurfaceHolder.lockCanvas(null);
+                    synchronized (mSurfaceHolder) {
+                        if (mMode == STATE_RUNNING) updatePhysics();
+                        doDraw(c);
+                    }
+                } finally {
+                    // do this in a finally so that if an exception is thrown
+                    // during the above, we don't leave the Surface in an
+                    // inconsistent state
+                    if (c != null) {
+                        mSurfaceHolder.unlockCanvasAndPost(c);
+                    }
+                }
+            }
+        }
+
+        /**
+         * Dump game state to the provided Bundle. Typically called when the
+         * Activity is being suspended.
+         * 
+         * @return Bundle with this view's state
+         */
+        public Bundle saveState(Bundle map) {
+            synchronized (mSurfaceHolder) {
+                if (map != null) {
+                    map.putInt(KEY_DIFFICULTY, Integer.valueOf(mDifficulty));
+                    map.putDouble(KEY_X, Double.valueOf(mX));
+                    map.putDouble(KEY_Y, Double.valueOf(mY));
+                    map.putDouble(KEY_DX, Double.valueOf(mDX));
+                    map.putDouble(KEY_DY, Double.valueOf(mDY));
+                    map.putDouble(KEY_HEADING, Double.valueOf(mHeading));
+                    map.putInt(KEY_LANDER_WIDTH, Integer.valueOf(mLanderWidth));
+                    map.putInt(KEY_LANDER_HEIGHT, Integer
+                            .valueOf(mLanderHeight));
+                    map.putInt(KEY_GOAL_X, Integer.valueOf(mGoalX));
+                    map.putInt(KEY_GOAL_SPEED, Integer.valueOf(mGoalSpeed));
+                    map.putInt(KEY_GOAL_ANGLE, Integer.valueOf(mGoalAngle));
+                    map.putInt(KEY_GOAL_WIDTH, Integer.valueOf(mGoalWidth));
+                    map.putInt(KEY_WINS, Integer.valueOf(mWinsInARow));
+                    map.putDouble(KEY_FUEL, Double.valueOf(mFuel));
+                }
+            }
+            return map;
+        }
+
+        /**
+         * Sets the current difficulty.
+         * 
+         * @param difficulty
+         */
+        public void setDifficulty(int difficulty) {
+            synchronized (mSurfaceHolder) {
+                mDifficulty = difficulty;
+            }
+        }
+
+        /**
+         * Sets if the engine is currently firing.
+         */
+        public void setFiring(boolean firing) {
+            synchronized (mSurfaceHolder) {
+                mEngineFiring = firing;
+            }
+        }
+
+        /**
+         * Used to signal the thread whether it should be running or not.
+         * Passing true allows the thread to run; passing false will shut it
+         * down if it's already running. Calling start() after this was most
+         * recently called with false will result in an immediate shutdown.
+         * 
+         * @param b true to run, false to shut down
+         */
+        public void setRunning(boolean b) {
+            mRun = b;
+        }
+
+        /**
+         * Sets the game mode. That is, whether we are running, paused, in the
+         * failure state, in the victory state, etc.
+         * 
+         * @see #setState(int, CharSequence)
+         * @param mode one of the STATE_* constants
+         */
+        public void setState(int mode) {
+            synchronized (mSurfaceHolder) {
+                setState(mode, null);
+            }
+        }
+
+        /**
+         * Sets the game mode. That is, whether we are running, paused, in the
+         * failure state, in the victory state, etc.
+         * 
+         * @param mode one of the STATE_* constants
+         * @param message string to add to screen or null
+         */
+        public void setState(int mode, CharSequence message) {
+            /*
+             * This method optionally can cause a text message to be displayed
+             * to the user when the mode changes. Since the View that actually
+             * renders that text is part of the main View hierarchy and not
+             * owned by this thread, we can't touch the state of that View.
+             * Instead we use a Message + Handler to relay commands to the main
+             * thread, which updates the user-text View.
+             */
+            synchronized (mSurfaceHolder) {
+                mMode = mode;
+
+                if (mMode == STATE_RUNNING) {
+                    Message msg = mHandler.obtainMessage();
+                    Bundle b = new Bundle();
+                    b.putString("text", "");
+                    b.putInt("viz", View.INVISIBLE);
+                    msg.setData(b);
+                    mHandler.sendMessage(msg);
+                } else {
+                    mRotating = 0;
+                    mEngineFiring = false;
+                    Resources res = mContext.getResources();
+                    CharSequence str = "";
+                    if (mMode == STATE_READY)
+                        str = res.getText(R.string.mode_ready);
+                    else if (mMode == STATE_PAUSE)
+                        str = res.getText(R.string.mode_pause);
+                    else if (mMode == STATE_LOSE)
+                        str = res.getText(R.string.mode_lose);
+                    else if (mMode == STATE_WIN)
+                        str = res.getString(R.string.mode_win_prefix)
+                                + mWinsInARow + " "
+                                + res.getString(R.string.mode_win_suffix);
+
+                    if (message != null) {
+                        str = message + "\n" + str;
+                    }
+
+                    if (mMode == STATE_LOSE) mWinsInARow = 0;
+
+                    Message msg = mHandler.obtainMessage();
+                    Bundle b = new Bundle();
+                    b.putString("text", str.toString());
+                    b.putInt("viz", View.VISIBLE);
+                    msg.setData(b);
+                    mHandler.sendMessage(msg);
+                }
+            }
+        }
+
+        /* Callback invoked when the surface dimensions change. */
+        public void setSurfaceSize(int width, int height) {
+            // synchronized to make sure these all change atomically
+            synchronized (mSurfaceHolder) {
+                mCanvasWidth = width;
+                mCanvasHeight = height;
+
+                // don't forget to resize the background image
+                mBackgroundImage = Bitmap.createScaledBitmap(mBackgroundImage, width, height, true);
+            }
+        }
+
+        /**
+         * Resumes from a pause.
+         */
+        public void unpause() {
+            // Move the real time clock up to now
+            synchronized (mSurfaceHolder) {
+                mLastTime = System.currentTimeMillis() + 100;
+            }
+            setState(STATE_RUNNING);
+        }
+
+        /**
+         * Handles a key-down event.
+         * 
+         * @param keyCode the key that was pressed
+         * @param msg the original event object
+         * @return true
+         */
+        boolean doKeyDown(int keyCode, KeyEvent msg) {
+            synchronized (mSurfaceHolder) {
+                boolean okStart = false;
+                if (keyCode == KeyEvent.KEYCODE_DPAD_UP) okStart = true;
+                if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) okStart = true;
+                if (keyCode == KeyEvent.KEYCODE_S) okStart = true;
+
+                if (okStart
+                        && (mMode == STATE_READY || mMode == STATE_LOSE || mMode == STATE_WIN)) {
+                    // ready-to-start -> start
+                    doStart();
+                    return true;
+                } else if (mMode == STATE_PAUSE && okStart) {
+                    // paused -> running
+                    unpause();
+                    return true;
+                } else if (mMode == STATE_RUNNING) {
+                    // center/space -> fire
+                    if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+                            || keyCode == KeyEvent.KEYCODE_SPACE) {
+                        setFiring(true);
+                        return true;
+                        // left/q -> left
+                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+                            || keyCode == KeyEvent.KEYCODE_Q) {
+                        mRotating = -1;
+                        return true;
+                        // right/w -> right
+                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+                            || keyCode == KeyEvent.KEYCODE_W) {
+                        mRotating = 1;
+                        return true;
+                        // up -> pause
+                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
+                        pause();
+                        return true;
+                    }
+                }
+
+                return false;
+            }
+        }
+
+        /**
+         * Handles a key-up event.
+         * 
+         * @param keyCode the key that was pressed
+         * @param msg the original event object
+         * @return true if the key was handled and consumed, or else false
+         */
+        boolean doKeyUp(int keyCode, KeyEvent msg) {
+            boolean handled = false;
+
+            synchronized (mSurfaceHolder) {
+                if (mMode == STATE_RUNNING) {
+                    if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER
+                            || keyCode == KeyEvent.KEYCODE_SPACE) {
+                        setFiring(false);
+                        handled = true;
+                    } else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
+                            || keyCode == KeyEvent.KEYCODE_Q
+                            || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT
+                            || keyCode == KeyEvent.KEYCODE_W) {
+                        mRotating = 0;
+                        handled = true;
+                    }
+                }
+            }
+
+            return handled;
+        }
+
+        /**
+         * Draws the ship, fuel/speed bars, and background to the provided
+         * Canvas.
+         */
+        private void doDraw(Canvas canvas) {
+            // Draw the background image. Operations on the Canvas accumulate
+            // so this is like clearing the screen.
+            canvas.drawBitmap(mBackgroundImage, 0, 0, null);
+
+            int yTop = mCanvasHeight - ((int) mY + mLanderHeight / 2);
+            int xLeft = (int) mX - mLanderWidth / 2;
+
+            // Draw the fuel gauge
+            int fuelWidth = (int) (UI_BAR * mFuel / PHYS_FUEL_MAX);
+            mScratchRect.set(4, 4, 4 + fuelWidth, 4 + UI_BAR_HEIGHT);
+            canvas.drawRect(mScratchRect, mLinePaint);
+
+            // Draw the speed gauge, with a two-tone effect
+            double speed = Math.sqrt(mDX * mDX + mDY * mDY);
+            int speedWidth = (int) (UI_BAR * speed / PHYS_SPEED_MAX);
+
+            if (speed <= mGoalSpeed) {
+                mScratchRect.set(4 + UI_BAR + 4, 4,
+                        4 + UI_BAR + 4 + speedWidth, 4 + UI_BAR_HEIGHT);
+                canvas.drawRect(mScratchRect, mLinePaint);
+            } else {
+                // Draw the bad color in back, with the good color in front of
+                // it
+                mScratchRect.set(4 + UI_BAR + 4, 4,
+                        4 + UI_BAR + 4 + speedWidth, 4 + UI_BAR_HEIGHT);
+                canvas.drawRect(mScratchRect, mLinePaintBad);
+                int goalWidth = (UI_BAR * mGoalSpeed / PHYS_SPEED_MAX);
+                mScratchRect.set(4 + UI_BAR + 4, 4, 4 + UI_BAR + 4 + goalWidth,
+                        4 + UI_BAR_HEIGHT);
+                canvas.drawRect(mScratchRect, mLinePaint);
+            }
+
+            // Draw the landing pad
+            canvas.drawLine(mGoalX, 1 + mCanvasHeight - TARGET_PAD_HEIGHT,
+                    mGoalX + mGoalWidth, 1 + mCanvasHeight - TARGET_PAD_HEIGHT,
+                    mLinePaint);
+
+
+            // Draw the ship with its current rotation
+            canvas.save();
+            canvas.rotate((float) mHeading, (float) mX, mCanvasHeight
+                    - (float) mY);
+            if (mMode == STATE_LOSE) {
+                mCrashedImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
+                        + mLanderHeight);
+                mCrashedImage.draw(canvas);
+            } else if (mEngineFiring) {
+                mFiringImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
+                        + mLanderHeight);
+                mFiringImage.draw(canvas);
+            } else {
+                mLanderImage.setBounds(xLeft, yTop, xLeft + mLanderWidth, yTop
+                        + mLanderHeight);
+                mLanderImage.draw(canvas);
+            }
+            canvas.restore();
+        }
+
+        /**
+         * Figures the lander state (x, y, fuel, ...) based on the passage of
+         * realtime. Does not invalidate(). Called at the start of draw().
+         * Detects the end-of-game and sets the UI to the next state.
+         */
+        private void updatePhysics() {
+            long now = System.currentTimeMillis();
+
+            // Do nothing if mLastTime is in the future.
+            // This allows the game-start to delay the start of the physics
+            // by 100ms or whatever.
+            if (mLastTime > now) return;
+
+            double elapsed = (now - mLastTime) / 1000.0;
+
+            // mRotating -- update heading
+            if (mRotating != 0) {
+                mHeading += mRotating * (PHYS_SLEW_SEC * elapsed);
+
+                // Bring things back into the range 0..360
+                if (mHeading < 0)
+                    mHeading += 360;
+                else if (mHeading >= 360) mHeading -= 360;
+            }
+
+            // Base accelerations -- 0 for x, gravity for y
+            double ddx = 0.0;
+            double ddy = -PHYS_DOWN_ACCEL_SEC * elapsed;
+
+            if (mEngineFiring) {
+                // taking 0 as up, 90 as to the right
+                // cos(deg) is ddy component, sin(deg) is ddx component
+                double elapsedFiring = elapsed;
+                double fuelUsed = elapsedFiring * PHYS_FUEL_SEC;
+
+                // tricky case where we run out of fuel partway through the
+                // elapsed
+                if (fuelUsed > mFuel) {
+                    elapsedFiring = mFuel / fuelUsed * elapsed;
+                    fuelUsed = mFuel;
+
+                    // Oddball case where we adjust the "control" from here
+                    mEngineFiring = false;
+                }
+
+                mFuel -= fuelUsed;
+
+                // have this much acceleration from the engine
+                double accel = PHYS_FIRE_ACCEL_SEC * elapsedFiring;
+
+                double radians = 2 * Math.PI * mHeading / 360;
+                ddx = Math.sin(radians) * accel;
+                ddy += Math.cos(radians) * accel;
+            }
+
+            double dxOld = mDX;
+            double dyOld = mDY;
+
+            // figure speeds for the end of the period
+            mDX += ddx;
+            mDY += ddy;
+
+            // figure position based on average speed during the period
+            mX += elapsed * (mDX + dxOld) / 2;
+            mY += elapsed * (mDY + dyOld) / 2;
+
+            mLastTime = now;
+
+            // Evaluate if we have landed ... stop the game
+            double yLowerBound = TARGET_PAD_HEIGHT + mLanderHeight / 2
+                    - TARGET_BOTTOM_PADDING;
+            if (mY <= yLowerBound) {
+                mY = yLowerBound;
+
+                int result = STATE_LOSE;
+                CharSequence message = "";
+                Resources res = mContext.getResources();
+                double speed = Math.sqrt(mDX * mDX + mDY * mDY);
+                boolean onGoal = (mGoalX <= mX - mLanderWidth / 2 && mX
+                        + mLanderWidth / 2 <= mGoalX + mGoalWidth);
+
+                // "Hyperspace" win -- upside down, going fast,
+                // puts you back at the top.
+                if (onGoal && Math.abs(mHeading - 180) < mGoalAngle
+                        && speed > PHYS_SPEED_HYPERSPACE) {
+                    result = STATE_WIN;
+                    mWinsInARow++;
+                    doStart();
+
+                    return;
+                    // Oddball case: this case does a return, all other cases
+                    // fall through to setMode() below.
+                } else if (!onGoal) {
+                    message = res.getText(R.string.message_off_pad);
+                } else if (!(mHeading <= mGoalAngle || mHeading >= 360 - mGoalAngle)) {
+                    message = res.getText(R.string.message_bad_angle);
+                } else if (speed > mGoalSpeed) {
+                    message = res.getText(R.string.message_too_fast);
+                } else {
+                    result = STATE_WIN;
+                    mWinsInARow++;
+                }
+
+                setState(result, message);
+            }
+        }
+    }
+
+    /** Handle to the application context, used to e.g. fetch Drawables. */
+    private Context mContext;
+
+    /** Pointer to the text view to display "Paused.." etc. */
+    private TextView mStatusText;
+
+    /** The thread that actually draws the animation */
+    private LunarThread thread;
+
+    public LunarView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        // register our interest in hearing about changes to our surface
+        SurfaceHolder holder = getHolder();
+        holder.addCallback(this);
+
+        // create thread only; it's started in surfaceCreated()
+        thread = new LunarThread(holder, context, new Handler() {
+            @Override
+            public void handleMessage(Message m) {
+                mStatusText.setVisibility(m.getData().getInt("viz"));
+                mStatusText.setText(m.getData().getString("text"));
+            }
+        });
+
+        setFocusable(true); // make sure we get key events
+    }
+
+    /**
+     * Fetches the animation thread corresponding to this LunarView.
+     * 
+     * @return the animation thread
+     */
+    public LunarThread getThread() {
+        return thread;
+    }
+
+    /**
+     * Standard override to get key-press events.
+     */
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent msg) {
+        return thread.doKeyDown(keyCode, msg);
+    }
+
+    /**
+     * Standard override for key-up. We actually care about these, so we can
+     * turn off the engine or stop rotating.
+     */
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent msg) {
+        return thread.doKeyUp(keyCode, msg);
+    }
+
+    /**
+     * Standard window-focus override. Notice focus lost so we can pause on
+     * focus lost. e.g. user switches to take a call.
+     */
+    @Override
+    public void onWindowFocusChanged(boolean hasWindowFocus) {
+        if (!hasWindowFocus) thread.pause();
+    }
+
+    /**
+     * Installs a pointer to the text view used for messages.
+     */
+    public void setTextView(TextView textView) {
+        mStatusText = textView;
+    }
+
+    /* Callback invoked when the surface dimensions change. */
+    public void surfaceChanged(SurfaceHolder holder, int format, int width,
+            int height) {
+        thread.setSurfaceSize(width, height);
+    }
+
+    /*
+     * Callback invoked when the Surface has been created and is ready to be
+     * used.
+     */
+    public void surfaceCreated(SurfaceHolder holder) {
+        // start the thread here so that we don't busy-wait in run()
+        // waiting for the surface to be created
+        thread.setRunning(true);
+        thread.start();
+    }
+
+    /*
+     * Callback invoked when the Surface has been destroyed and must no longer
+     * be touched. WARNING: after this method returns, the Surface/Canvas must
+     * never be touched again!
+     */
+    public void surfaceDestroyed(SurfaceHolder holder) {
+        // we have to tell thread to shut down & wait for it to finish, or else
+        // it might touch the Surface after we return and explode
+        boolean retry = true;
+        thread.setRunning(false);
+        while (retry) {
+            try {
+                thread.join();
+                retry = false;
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+}
Index: src/com/example/helloandroid/Planet.java
===================================================================
--- src/com/example/helloandroid/Planet.java	(revision 1a91f0dcdf2b97c5a72bf534516f7a8af58cbdb2)
+++ src/com/example/helloandroid/Planet.java	(revision 3e9f39e1d72da6ea6f30f83ad48dc9636cd7964a)
@@ -1,9 +1,11 @@
 package com.example.helloandroid;
+
+import java.util.ArrayList;
 
 public class Planet {
 	int radius;
 	int regenRate;	// ships per second
-	int x;
-	int y;
+	private int x;
+	private int y;
 	int faction;
 	int numShips;
@@ -27,6 +29,19 @@
 	}
 	
+	public int getRadius() {
+		return radius;
+	}
+	
+	public int getNumShips() {
+		return numShips;
+	}
+	
+	public void setNumShips(int num) {
+		numShips = num;
+	}
+	
 	public void update() {
 		//regen ships if not owned by faction 0
+		numShips++;
 	}
 	
@@ -34,3 +49,18 @@
 		
 	}
+	
+	public boolean collides(Planet p) {
+		double dist = Math.sqrt(Math.pow(this.x-p.x, 2) + Math.pow(this.y-p.y, 2));
+		
+		return dist <= this.radius + p.radius;
+	}
+	
+	public static boolean collisionDetected(Planet p, ArrayList<Planet> curPlanets) {
+		for(Planet p2 : curPlanets) {
+			if(p.collides(p2))
+				return true;
+		}
+		
+		return false;
+	}
 }
