Home | History | Annotate | Download | only in jetboy
      1 /*
      2  * Copyright (C) 2009 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  *
     16  */
     17 
     18 // Android JET demonstration code:
     19 // All inline comments related to the use of the JetPlayer class are preceded by "JET info:"
     20 
     21 package com.example.android.jetboy;
     22 
     23 import android.content.Context;
     24 import android.content.res.Resources;
     25 import android.graphics.Bitmap;
     26 import android.graphics.BitmapFactory;
     27 import android.graphics.Canvas;
     28 import android.media.JetPlayer;
     29 import android.media.JetPlayer.OnJetEventListener;
     30 import android.os.Bundle;
     31 import android.os.Handler;
     32 import android.os.Message;
     33 import android.util.AttributeSet;
     34 import android.util.Log;
     35 import android.view.KeyEvent;
     36 import android.view.SurfaceHolder;
     37 import android.view.SurfaceView;
     38 import android.view.View;
     39 import android.widget.Button;
     40 import android.widget.TextView;
     41 
     42 import java.util.Random;
     43 import java.util.Timer;
     44 import java.util.TimerTask;
     45 import java.util.Vector;
     46 import java.util.concurrent.ConcurrentLinkedQueue;
     47 
     48 public class JetBoyView extends SurfaceView implements SurfaceHolder.Callback {
     49 
     50     // the number of asteroids that must be destroyed
     51     public static final int mSuccessThreshold = 50;
     52 
     53     // used to calculate level for mutes and trigger clip
     54     public int mHitStreak = 0;
     55 
     56     // total number asteroids you need to hit.
     57     public int mHitTotal = 0;
     58 
     59     // which music bed is currently playing?
     60     public int mCurrentBed = 0;
     61 
     62     // a lazy graphic fudge for the initial title splash
     63     private Bitmap mTitleBG;
     64 
     65     private Bitmap mTitleBG2;
     66 
     67     /**
     68      * Base class for any external event passed to the JetBoyThread. This can
     69      * include user input, system events, network input, etc.
     70      */
     71     class GameEvent {
     72         public GameEvent() {
     73             eventTime = System.currentTimeMillis();
     74         }
     75 
     76         long eventTime;
     77     }
     78 
     79     /**
     80      * A GameEvent subclass for key based user input. Values are those used by
     81      * the standard onKey
     82      */
     83     class KeyGameEvent extends GameEvent {
     84         /**
     85          * Simple constructor to make populating this event easier.
     86          */
     87         public KeyGameEvent(int keyCode, boolean up, KeyEvent msg) {
     88             this.keyCode = keyCode;
     89             this.msg = msg;
     90             this.up = up;
     91         }
     92 
     93         public int keyCode;
     94         public KeyEvent msg;
     95         public boolean up;
     96     }
     97 
     98     /**
     99      * A GameEvent subclass for events from the JetPlayer.
    100      */
    101     class JetGameEvent extends GameEvent {
    102         /**
    103          * Simple constructor to make populating this event easier.
    104          */
    105         public JetGameEvent(JetPlayer player, short segment, byte track, byte channel,
    106                 byte controller, byte value) {
    107             this.player = player;
    108             this.segment = segment;
    109             this.track = track;
    110             this.channel = channel;
    111             this.controller = controller;
    112             this.value = value;
    113         }
    114 
    115         public JetPlayer player;
    116         public short segment;
    117         public byte track;
    118         public byte channel;
    119         public byte controller;
    120         public byte value;
    121     }
    122 
    123     // JET info: the JetBoyThread receives all the events from the JET player
    124     // JET info: through the OnJetEventListener interface.
    125     class JetBoyThread extends Thread implements OnJetEventListener {
    126 
    127         /**
    128          * State-tracking constants.
    129          */
    130         public static final int STATE_START = -1;
    131         public static final int STATE_PLAY = 0;
    132         public static final int STATE_LOSE = 1;
    133         public static final int STATE_PAUSE = 2;
    134         public static final int STATE_RUNNING = 3;
    135 
    136         // how many frames per beat? The basic animation can be changed for
    137         // instance to 3/4 by changing this to 3.
    138         // untested is the impact on other parts of game logic for non 4/4 time.
    139         private static final int ANIMATION_FRAMES_PER_BEAT = 4;
    140 
    141         public boolean mInitialized = false;
    142 
    143         /** Queue for GameEvents */
    144         protected ConcurrentLinkedQueue<GameEvent> mEventQueue = new ConcurrentLinkedQueue<GameEvent>();
    145 
    146         /** Context for processKey to maintain state accross frames * */
    147         protected Object mKeyContext = null;
    148 
    149         // the timer display in seconds
    150         public int mTimerLimit;
    151 
    152         // used for internal timing logic.
    153         public final int TIMER_LIMIT = 72;
    154 
    155         // string value for timer display
    156         private String mTimerValue = "1:12";
    157 
    158         // start, play, running, lose are the states we use
    159         public int mState;
    160 
    161         // has laser been fired and for how long?
    162         // user for fx logic on laser fire
    163         boolean mLaserOn = false;
    164 
    165         long mLaserFireTime = 0;
    166 
    167         /** The drawable to use as the far background of the animation canvas */
    168         private Bitmap mBackgroundImageFar;
    169 
    170         /** The drawable to use as the close background of the animation canvas */
    171         private Bitmap mBackgroundImageNear;
    172 
    173         // JET info: event IDs within the JET file.
    174         // JET info: in this game 80 is used for sending asteroid across the screen
    175         // JET info: 82 is used as game time for 1/4 note beat.
    176         private final byte NEW_ASTEROID_EVENT = 80;
    177         private final byte TIMER_EVENT = 82;
    178 
    179         // used to track beat for synch of mute/unmute actions
    180         private int mBeatCount = 1;
    181 
    182         // our intrepid space boy
    183         private Bitmap[] mShipFlying = new Bitmap[4];
    184 
    185         // the twinkly bit
    186         private Bitmap[] mBeam = new Bitmap[4];
    187 
    188         // the things you are trying to hit
    189         private Bitmap[] mAsteroids = new Bitmap[12];
    190 
    191         // hit animation
    192         private Bitmap[] mExplosions = new Bitmap[4];
    193 
    194         private Bitmap mTimerShell;
    195 
    196         private Bitmap mLaserShot;
    197 
    198         // used to save the beat event system time.
    199         private long mLastBeatTime;
    200 
    201         private long mPassedTime;
    202 
    203         // how much do we move the asteroids per beat?
    204         private int mPixelMoveX = 25;
    205 
    206         // the asteroid send events are generated from the Jet File.
    207         // but which land they start in is random.
    208         private Random mRandom = new Random();
    209 
    210         // JET info: the star of our show, a reference to the JetPlayer object.
    211         private JetPlayer mJet = null;
    212 
    213         private boolean mJetPlaying = false;
    214 
    215         /** Message handler used by thread to interact with TextView */
    216         private Handler mHandler;
    217 
    218         /** Handle to the surface manager object we interact with */
    219         private SurfaceHolder mSurfaceHolder;
    220 
    221         /** Handle to the application context, used to e.g. fetch Drawables. */
    222         private Context mContext;
    223 
    224         /** Indicate whether the surface has been created & is ready to draw */
    225         private boolean mRun = false;
    226 
    227         // updates the screen clock. Also used for tempo timing.
    228         private Timer mTimer = null;
    229 
    230         private TimerTask mTimerTask = null;
    231 
    232         // one second - used to update timer
    233         private int mTaskIntervalInMillis = 1000;
    234 
    235         /**
    236          * Current height of the surface/canvas.
    237          *
    238          * @see #setSurfaceSize
    239          */
    240         private int mCanvasHeight = 1;
    241 
    242         /**
    243          * Current width of the surface/canvas.
    244          *
    245          * @see #setSurfaceSize
    246          */
    247         private int mCanvasWidth = 1;
    248 
    249         // used to track the picture to draw for ship animation
    250         private int mShipIndex = 0;
    251 
    252         // stores all of the asteroid objects in order
    253         private Vector<Asteroid> mDangerWillRobinson;
    254 
    255         private Vector<Explosion> mExplosion;
    256 
    257         // right to left scroll tracker for near and far BG
    258         private int mBGFarMoveX = 0;
    259         private int mBGNearMoveX = 0;
    260 
    261         // how far up (close to top) jet boy can fly
    262         private int mJetBoyYMin = 40;
    263         private int mJetBoyX = 0;
    264         private int mJetBoyY = 0;
    265 
    266         // this is the pixel position of the laser beam guide.
    267         private int mAsteroidMoveLimitX = 110;
    268 
    269         // how far up asteroid can be painted
    270         private int mAsteroidMinY = 40;
    271 
    272 
    273         Resources mRes;
    274 
    275         // array to store the mute masks that are applied during game play to respond to
    276         // the player's hit streaks
    277         private boolean muteMask[][] = new boolean[9][32];
    278 
    279         /**
    280          * This is the constructor for the main worker bee
    281          *
    282          * @param surfaceHolder
    283          * @param context
    284          * @param handler
    285          */
    286         public JetBoyThread(SurfaceHolder surfaceHolder, Context context, Handler handler) {
    287 
    288             mSurfaceHolder = surfaceHolder;
    289             mHandler = handler;
    290             mContext = context;
    291             mRes = context.getResources();
    292 
    293             // JET info: this are the mute arrays associated with the music beds in the
    294             // JET info: JET file
    295             for (int ii = 0; ii < 8; ii++) {
    296                 for (int xx = 0; xx < 32; xx++) {
    297                     muteMask[ii][xx] = true;
    298                 }
    299             }
    300 
    301             muteMask[0][2] = false;
    302             muteMask[0][3] = false;
    303             muteMask[0][4] = false;
    304             muteMask[0][5] = false;
    305 
    306             muteMask[1][2] = false;
    307             muteMask[1][3] = false;
    308             muteMask[1][4] = false;
    309             muteMask[1][5] = false;
    310             muteMask[1][8] = false;
    311             muteMask[1][9] = false;
    312 
    313             muteMask[2][2] = false;
    314             muteMask[2][3] = false;
    315             muteMask[2][6] = false;
    316             muteMask[2][7] = false;
    317             muteMask[2][8] = false;
    318             muteMask[2][9] = false;
    319 
    320             muteMask[3][2] = false;
    321             muteMask[3][3] = false;
    322             muteMask[3][6] = false;
    323             muteMask[3][11] = false;
    324             muteMask[3][12] = false;
    325 
    326             muteMask[4][2] = false;
    327             muteMask[4][3] = false;
    328             muteMask[4][10] = false;
    329             muteMask[4][11] = false;
    330             muteMask[4][12] = false;
    331             muteMask[4][13] = false;
    332 
    333             muteMask[5][2] = false;
    334             muteMask[5][3] = false;
    335             muteMask[5][10] = false;
    336             muteMask[5][12] = false;
    337             muteMask[5][15] = false;
    338             muteMask[5][17] = false;
    339 
    340             muteMask[6][2] = false;
    341             muteMask[6][3] = false;
    342             muteMask[6][14] = false;
    343             muteMask[6][15] = false;
    344             muteMask[6][16] = false;
    345             muteMask[6][17] = false;
    346 
    347             muteMask[7][2] = false;
    348             muteMask[7][3] = false;
    349             muteMask[7][6] = false;
    350             muteMask[7][14] = false;
    351             muteMask[7][15] = false;
    352             muteMask[7][16] = false;
    353             muteMask[7][17] = false;
    354             muteMask[7][18] = false;
    355 
    356             // set all tracks to play
    357             for (int xx = 0; xx < 32; xx++) {
    358                 muteMask[8][xx] = false;
    359             }
    360 
    361             // always set state to start, ensure we come in from front door if
    362             // app gets tucked into background
    363             mState = STATE_START;
    364 
    365             setInitialGameState();
    366 
    367             mTitleBG = BitmapFactory.decodeResource(mRes, R.drawable.title_hori);
    368 
    369             // load background image as a Bitmap instead of a Drawable b/c
    370             // we don't need to transform it and it's faster to draw this
    371             // way...thanks lunar lander :)
    372 
    373             // two background since we want them moving at different speeds
    374             mBackgroundImageFar = BitmapFactory.decodeResource(mRes, R.drawable.background_a);
    375 
    376             mLaserShot = BitmapFactory.decodeResource(mRes, R.drawable.laser);
    377 
    378             mBackgroundImageNear = BitmapFactory.decodeResource(mRes, R.drawable.background_b);
    379 
    380             mShipFlying[0] = BitmapFactory.decodeResource(mRes, R.drawable.ship2_1);
    381             mShipFlying[1] = BitmapFactory.decodeResource(mRes, R.drawable.ship2_2);
    382             mShipFlying[2] = BitmapFactory.decodeResource(mRes, R.drawable.ship2_3);
    383             mShipFlying[3] = BitmapFactory.decodeResource(mRes, R.drawable.ship2_4);
    384 
    385             mBeam[0] = BitmapFactory.decodeResource(mRes, R.drawable.intbeam_1);
    386             mBeam[1] = BitmapFactory.decodeResource(mRes, R.drawable.intbeam_2);
    387             mBeam[2] = BitmapFactory.decodeResource(mRes, R.drawable.intbeam_3);
    388             mBeam[3] = BitmapFactory.decodeResource(mRes, R.drawable.intbeam_4);
    389 
    390             mTimerShell = BitmapFactory.decodeResource(mRes, R.drawable.int_timer);
    391 
    392             // I wanted them to rotate in a certain way
    393             // so I loaded them backwards from the way created.
    394             mAsteroids[11] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid01);
    395             mAsteroids[10] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid02);
    396             mAsteroids[9] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid03);
    397             mAsteroids[8] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid04);
    398             mAsteroids[7] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid05);
    399             mAsteroids[6] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid06);
    400             mAsteroids[5] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid07);
    401             mAsteroids[4] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid08);
    402             mAsteroids[3] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid09);
    403             mAsteroids[2] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid10);
    404             mAsteroids[1] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid11);
    405             mAsteroids[0] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid12);
    406 
    407             mExplosions[0] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid_explode1);
    408             mExplosions[1] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid_explode2);
    409             mExplosions[2] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid_explode3);
    410             mExplosions[3] = BitmapFactory.decodeResource(mRes, R.drawable.asteroid_explode4);
    411 
    412         }
    413 
    414         /**
    415          * Does the grunt work of setting up initial jet requirements
    416          */
    417         private void initializeJetPlayer() {
    418 
    419             // JET info: let's create our JetPlayer instance using the factory.
    420             // JET info: if we already had one, the same singleton is returned.
    421             mJet = JetPlayer.getJetPlayer();
    422 
    423             mJetPlaying = false;
    424 
    425             // JET info: make sure we flush the queue,
    426             // JET info: otherwise left over events from previous gameplay can hang around.
    427             // JET info: ok, here we don't really need that but if you ever reuse a JetPlayer
    428             // JET info: instance, clear the queue before reusing it, this will also clear any
    429             // JET info: trigger clips that have been triggered but not played yet.
    430             mJet.clearQueue();
    431 
    432             // JET info: we are going to receive in this example all the JET callbacks
    433             // JET info: inthis animation thread object.
    434             mJet.setEventListener(this);
    435 
    436             Log.d(TAG, "opening jet file");
    437 
    438             // JET info: load the actual JET content the game will be playing,
    439             // JET info: it's stored as a raw resource in our APK, and is labeled "level1"
    440             mJet.loadJetFile(mContext.getResources().openRawResourceFd(R.raw.level1));
    441             // JET info: if our JET file was stored on the sdcard for instance, we would have used
    442             // JET info: mJet.loadJetFile("/sdcard/level1.jet");
    443 
    444             Log.d(TAG, "opening jet file DONE");
    445 
    446             mCurrentBed = 0;
    447             byte sSegmentID = 0;
    448 
    449             Log.d(TAG, " start queuing jet file");
    450 
    451             // JET info: now we're all set to prepare queuing the JET audio segments for the game.
    452             // JET info: in this example, the game uses segment 0 for the duration of the game play,
    453             // JET info: and plays segment 1 several times as the "outro" music, so we're going to
    454             // JET info: queue everything upfront, but with more complex JET compositions, we could
    455             // JET info: also queue the segments during the game play.
    456 
    457             // JET info: this is the main game play music
    458             // JET info: it is located at segment 0
    459             // JET info: it uses the first DLS lib in the .jet resource, which is at index 0
    460             // JET info: index -1 means no DLS
    461             mJet.queueJetSegment(0, 0, 0, 0, 0, sSegmentID);
    462 
    463             // JET info: end game music, loop 4 times normal pitch
    464             mJet.queueJetSegment(1, 0, 4, 0, 0, sSegmentID);
    465 
    466             // JET info: end game music loop 4 times up an octave
    467             mJet.queueJetSegment(1, 0, 4, 1, 0, sSegmentID);
    468 
    469             // JET info: set the mute mask as designed for the beginning of the game, when the
    470             // JET info: the player hasn't scored yet.
    471             mJet.setMuteArray(muteMask[0], true);
    472 
    473             Log.d(TAG, " start queuing jet file DONE");
    474 
    475         }
    476 
    477 
    478         private void doDraw(Canvas canvas) {
    479 
    480             if (mState == STATE_RUNNING) {
    481                 doDrawRunning(canvas);
    482             } else if (mState == STATE_START) {
    483                 doDrawReady(canvas);
    484             } else if (mState == STATE_PLAY || mState == STATE_LOSE) {
    485                 if (mTitleBG2 == null) {
    486                     mTitleBG2 = BitmapFactory.decodeResource(mRes, R.drawable.title_bg_hori);
    487                 }
    488                 doDrawPlay(canvas);
    489             }// end state play block
    490         }
    491 
    492 
    493         /**
    494          * Draws current state of the game Canvas.
    495          */
    496         private void doDrawRunning(Canvas canvas) {
    497 
    498             // decrement the far background
    499             mBGFarMoveX = mBGFarMoveX - 1;
    500 
    501             // decrement the near background
    502             mBGNearMoveX = mBGNearMoveX - 4;
    503 
    504             // calculate the wrap factor for matching image draw
    505             int newFarX = mBackgroundImageFar.getWidth() - (-mBGFarMoveX);
    506 
    507             // if we have scrolled all the way, reset to start
    508             if (newFarX <= 0) {
    509                 mBGFarMoveX = 0;
    510                 // only need one draw
    511                 canvas.drawBitmap(mBackgroundImageFar, mBGFarMoveX, 0, null);
    512 
    513             } else {
    514                 // need to draw original and wrap
    515                 canvas.drawBitmap(mBackgroundImageFar, mBGFarMoveX, 0, null);
    516                 canvas.drawBitmap(mBackgroundImageFar, newFarX, 0, null);
    517             }
    518 
    519             // same story different image...
    520             // TODO possible method call
    521             int newNearX = mBackgroundImageNear.getWidth() - (-mBGNearMoveX);
    522 
    523             if (newNearX <= 0) {
    524                 mBGNearMoveX = 0;
    525                 canvas.drawBitmap(mBackgroundImageNear, mBGNearMoveX, 0, null);
    526 
    527             } else {
    528                 canvas.drawBitmap(mBackgroundImageNear, mBGNearMoveX, 0, null);
    529                 canvas.drawBitmap(mBackgroundImageNear, newNearX, 0, null);
    530             }
    531 
    532             doAsteroidAnimation(canvas);
    533 
    534             canvas.drawBitmap(mBeam[mShipIndex], 51 + 20, 0, null);
    535 
    536             mShipIndex++;
    537 
    538             if (mShipIndex == 4)
    539                 mShipIndex = 0;
    540 
    541             // draw the space ship in the same lane as the next asteroid
    542             canvas.drawBitmap(mShipFlying[mShipIndex], mJetBoyX, mJetBoyY, null);
    543 
    544             if (mLaserOn) {
    545                 canvas.drawBitmap(mLaserShot, mJetBoyX + mShipFlying[0].getWidth(), mJetBoyY
    546                         + (mShipFlying[0].getHeight() / 2), null);
    547             }
    548 
    549             // tick tock
    550             canvas.drawBitmap(mTimerShell, mCanvasWidth - mTimerShell.getWidth(), 0, null);
    551 
    552         }
    553 
    554         private void setInitialGameState() {
    555             mTimerLimit = TIMER_LIMIT;
    556 
    557             mJetBoyY = mJetBoyYMin;
    558 
    559             // set up jet stuff
    560             initializeJetPlayer();
    561 
    562             mTimer = new Timer();
    563 
    564             mDangerWillRobinson = new Vector<Asteroid>();
    565 
    566             mExplosion = new Vector<Explosion>();
    567 
    568             mInitialized = true;
    569 
    570             mHitStreak = 0;
    571             mHitTotal = 0;
    572         }
    573 
    574         private void doAsteroidAnimation(Canvas canvas) {
    575             if ((mDangerWillRobinson == null | mDangerWillRobinson.size() == 0)
    576                     && (mExplosion != null && mExplosion.size() == 0))
    577                 return;
    578 
    579             // Compute what percentage through a beat we are and adjust
    580             // animation and position based on that. This assumes 140bpm(428ms/beat).
    581             // This is just inter-beat interpolation, no game state is updated
    582             long frameDelta = System.currentTimeMillis() - mLastBeatTime;
    583 
    584             int animOffset = (int)(ANIMATION_FRAMES_PER_BEAT * frameDelta / 428);
    585 
    586             for (int i = (mDangerWillRobinson.size() - 1); i >= 0; i--) {
    587                 Asteroid asteroid = mDangerWillRobinson.elementAt(i);
    588 
    589                 if (!asteroid.mMissed)
    590                     mJetBoyY = asteroid.mDrawY;
    591 
    592                 // Log.d(TAG, " drawing asteroid " + ii + " at " +
    593                 // asteroid.mDrawX );
    594 
    595                 canvas.drawBitmap(
    596                         mAsteroids[(asteroid.mAniIndex + animOffset) % mAsteroids.length],
    597                         asteroid.mDrawX, asteroid.mDrawY, null);
    598             }
    599 
    600             for (int i = (mExplosion.size() - 1); i >= 0; i--) {
    601                 Explosion ex = mExplosion.elementAt(i);
    602 
    603                 canvas.drawBitmap(mExplosions[(ex.mAniIndex + animOffset) % mExplosions.length],
    604                         ex.mDrawX, ex.mDrawY, null);
    605             }
    606         }
    607 
    608         private void doDrawReady(Canvas canvas) {
    609             canvas.drawBitmap(mTitleBG, 0, 0, null);
    610         }
    611 
    612         private void doDrawPlay(Canvas canvas) {
    613             canvas.drawBitmap(mTitleBG2, 0, 0, null);
    614         }
    615 
    616 
    617         /**
    618          * the heart of the worker bee
    619          */
    620         public void run() {
    621             // while running do stuff in this loop...bzzz!
    622             while (mRun) {
    623                 Canvas c = null;
    624 
    625                 if (mState == STATE_RUNNING) {
    626                     // Process any input and apply it to the game state
    627                     updateGameState();
    628 
    629                     if (!mJetPlaying) {
    630 
    631                         mInitialized = false;
    632                         Log.d(TAG, "------> STARTING JET PLAY");
    633                         mJet.play();
    634 
    635                         mJetPlaying = true;
    636 
    637                     }
    638 
    639                     mPassedTime = System.currentTimeMillis();
    640 
    641                     // kick off the timer task for counter update if not already
    642                     // initialized
    643                     if (mTimerTask == null) {
    644                         mTimerTask = new TimerTask() {
    645                             public void run() {
    646                                 doCountDown();
    647                             }
    648                         };
    649 
    650                         mTimer.schedule(mTimerTask, mTaskIntervalInMillis);
    651 
    652                     }// end of TimerTask init block
    653 
    654                 }// end of STATE_RUNNING block
    655                 else if (mState == STATE_PLAY && !mInitialized)
    656                 {
    657                     setInitialGameState();
    658                 } else if (mState == STATE_LOSE) {
    659                     mInitialized = false;
    660                 }
    661 
    662                 try {
    663                     c = mSurfaceHolder.lockCanvas(null);
    664                     // synchronized (mSurfaceHolder) {
    665                     doDraw(c);
    666                     // }
    667                 } finally {
    668                     // do this in a finally so that if an exception is thrown
    669                     // during the above, we don't leave the Surface in an
    670                     // inconsistent state
    671                     if (c != null) {
    672                         mSurfaceHolder.unlockCanvasAndPost(c);
    673                     }
    674                 }// end finally block
    675             }// end while mrun block
    676         }
    677 
    678 
    679         /**
    680          * This method handles updating the model of the game state. No
    681          * rendering is done here only processing of inputs and update of state.
    682          * This includes positons of all game objects (asteroids, player,
    683          * explosions), their state (animation frame, hit), creation of new
    684          * objects, etc.
    685          */
    686         protected void updateGameState() {
    687             // Process any game events and apply them
    688             while (true) {
    689                 GameEvent event = mEventQueue.poll();
    690                 if (event == null)
    691                     break;
    692 
    693                 // Log.d(TAG,"*** EVENT = " + event);
    694 
    695                 // Process keys tracking the input context to pass in to later
    696                 // calls
    697                 if (event instanceof KeyGameEvent) {
    698                     // Process the key for affects other then asteroid hits
    699                     mKeyContext = processKeyEvent((KeyGameEvent)event, mKeyContext);
    700 
    701                     // Update laser state. Having this here allows the laser to
    702                     // be triggered right when the key is
    703                     // pressed. If we comment this out the laser will only be
    704                     // turned on when updateLaser is called
    705                     // when processing a timer event below.
    706                     updateLaser(mKeyContext);
    707 
    708                 }
    709                 // JET events trigger a state update
    710                 else if (event instanceof JetGameEvent) {
    711                     JetGameEvent jetEvent = (JetGameEvent)event;
    712 
    713                     // Only update state on a timer event
    714                     if (jetEvent.value == TIMER_EVENT) {
    715                         // Note the time of the last beat
    716                         mLastBeatTime = System.currentTimeMillis();
    717 
    718                         // Update laser state, turning it on if a key has been
    719                         // pressed or off if it has been
    720                         // on for too long.
    721                         updateLaser(mKeyContext);
    722 
    723                         // Update explosions before we update asteroids because
    724                         // updateAsteroids may add
    725                         // new explosions that we do not want updated until next
    726                         // frame
    727                         updateExplosions(mKeyContext);
    728 
    729                         // Update asteroid positions, hit status and animations
    730                         updateAsteroids(mKeyContext);
    731                     }
    732 
    733                     processJetEvent(jetEvent.player, jetEvent.segment, jetEvent.track,
    734                             jetEvent.channel, jetEvent.controller, jetEvent.value);
    735                 }
    736             }
    737         }
    738 
    739 
    740         /**
    741          * This method handles the state updates that can be caused by key press
    742          * events. Key events may mean different things depending on what has
    743          * come before, to support this concept this method takes an opaque
    744          * context object as a parameter and returns an updated version. This
    745          * context should be set to null for the first event then should be set
    746          * to the last value returned for subsequent events.
    747          */
    748         protected Object processKeyEvent(KeyGameEvent event, Object context) {
    749             // Log.d(TAG, "key code is " + event.keyCode + " " + (event.up ?
    750             // "up":"down"));
    751 
    752             // If it is a key up on the fire key make sure we mute the
    753             // associated sound
    754             if (event.up) {
    755                 if (event.keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
    756                     return null;
    757                 }
    758             }
    759             // If it is a key down on the fire key start playing the sound and
    760             // update the context
    761             // to indicate that a key has been pressed and to ignore further
    762             // presses
    763             else {
    764                 if (event.keyCode == KeyEvent.KEYCODE_DPAD_CENTER && (context == null)) {
    765                     return event;
    766                 }
    767             }
    768 
    769             // Return the context unchanged
    770             return context;
    771         }
    772 
    773 
    774         /**
    775          * This method updates the laser status based on user input and shot
    776          * duration
    777          */
    778         protected void updateLaser(Object inputContext) {
    779             // Lookup the time of the fire event if there is one
    780             long keyTime = inputContext == null ? 0 : ((GameEvent)inputContext).eventTime;
    781 
    782             // Log.d(TAG,"keyTime delta = " +
    783             // (System.currentTimeMillis()-keyTime) + ": obj = " +
    784             // inputContext);
    785 
    786             // If the laser has been on too long shut it down
    787             if (mLaserOn && System.currentTimeMillis() - mLaserFireTime > 400) {
    788                 mLaserOn = false;
    789             }
    790 
    791             // trying to tune the laser hit timing
    792             else if (System.currentTimeMillis() - mLaserFireTime > 300) {
    793                 // JET info: the laser sound is on track 23, we mute it (true) right away (false)
    794                 mJet.setMuteFlag(23, true, false);
    795 
    796             }
    797 
    798             // Now check to see if we should turn the laser on. We do this after
    799             // the above shutdown
    800             // logic so it can be turned back on in the same frame it was turned
    801             // off in. If we want
    802             // to add a cooldown period this may change.
    803             if (!mLaserOn && System.currentTimeMillis() - keyTime <= 400) {
    804 
    805                 mLaserOn = true;
    806                 mLaserFireTime = keyTime;
    807 
    808                 // JET info: unmute the laser track (false) right away (false)
    809                 mJet.setMuteFlag(23, false, false);
    810             }
    811         }
    812 
    813         /**
    814          * Update asteroid state including position and laser hit status.
    815          */
    816         protected void updateAsteroids(Object inputContext) {
    817             if (mDangerWillRobinson == null | mDangerWillRobinson.size() == 0)
    818                 return;
    819 
    820             for (int i = (mDangerWillRobinson.size() - 1); i >= 0; i--) {
    821                 Asteroid asteroid = mDangerWillRobinson.elementAt(i);
    822 
    823                 // If the asteroid is within laser range but not already missed
    824                 // check if the key was pressed close enough to the beat to make a hit
    825                 if (asteroid.mDrawX <= mAsteroidMoveLimitX + 20 && !asteroid.mMissed)
    826                 {
    827                     // If the laser was fired on the beat destroy the asteroid
    828                     if (mLaserOn) {
    829                         // Track hit streak for adjusting music
    830                         mHitStreak++;
    831                         mHitTotal++;
    832 
    833                         // replace the asteroid with an explosion
    834                         Explosion ex = new Explosion();
    835                         ex.mAniIndex = 0;
    836                         ex.mDrawX = asteroid.mDrawX;
    837                         ex.mDrawY = asteroid.mDrawY;
    838                         mExplosion.add(ex);
    839 
    840                         mJet.setMuteFlag(24, false, false);
    841 
    842                         mDangerWillRobinson.removeElementAt(i);
    843 
    844                         // This asteroid has been removed process the next one
    845                         continue;
    846                     } else {
    847                         // Sorry, timing was not good enough, mark the asteroid
    848                         // as missed so on next frame it cannot be hit even if it is still
    849                         // within range
    850                         asteroid.mMissed = true;
    851 
    852                         mHitStreak = mHitStreak - 1;
    853 
    854                         if (mHitStreak < 0)
    855                             mHitStreak = 0;
    856 
    857                     }
    858                 }
    859 
    860                 // Update the asteroids position, even missed ones keep moving
    861                 asteroid.mDrawX -= mPixelMoveX;
    862 
    863                 // Update asteroid animation frame
    864                 asteroid.mAniIndex = (asteroid.mAniIndex + ANIMATION_FRAMES_PER_BEAT)
    865                         % mAsteroids.length;
    866 
    867                 // if we have scrolled off the screen
    868                 if (asteroid.mDrawX < 0) {
    869                     mDangerWillRobinson.removeElementAt(i);
    870                 }
    871             }
    872         }
    873 
    874         /**
    875          * This method updates explosion animation and removes them once they
    876          * have completed.
    877          */
    878         protected void updateExplosions(Object inputContext) {
    879             if (mExplosion == null | mExplosion.size() == 0)
    880                 return;
    881 
    882             for (int i = mExplosion.size() - 1; i >= 0; i--) {
    883                 Explosion ex = mExplosion.elementAt(i);
    884 
    885                 ex.mAniIndex += ANIMATION_FRAMES_PER_BEAT;
    886 
    887                 // When the animation completes remove the explosion
    888                 if (ex.mAniIndex > 3) {
    889                     mJet.setMuteFlag(24, true, false);
    890                     mJet.setMuteFlag(23, true, false);
    891 
    892                     mExplosion.removeElementAt(i);
    893                 }
    894             }
    895         }
    896 
    897         /**
    898          * This method handles the state updates that can be caused by JET
    899          * events.
    900          */
    901         protected void processJetEvent(JetPlayer player, short segment, byte track, byte channel,
    902                 byte controller, byte value) {
    903 
    904             //Log.d(TAG, "onJetEvent(): seg=" + segment + " track=" + track + " chan=" + channel
    905             //        + " cntrlr=" + controller + " val=" + value);
    906 
    907 
    908             // Check for an event that triggers a new asteroid
    909             if (value == NEW_ASTEROID_EVENT) {
    910                 doAsteroidCreation();
    911             }
    912 
    913             mBeatCount++;
    914 
    915             if (mBeatCount > 4) {
    916                 mBeatCount = 1;
    917 
    918             }
    919 
    920             // Scale the music based on progress
    921 
    922             // it was a game requirement to change the mute array on 1st beat of
    923             // the next measure when needed
    924             // and so we track beat count, after that we track hitStreak to
    925             // determine the music "intensity"
    926             // if the intensity has go gone up, call a corresponding trigger clip, otherwise just
    927             // execute the rest of the music bed change logic.
    928             if (mBeatCount == 1) {
    929 
    930                 // do it back wards so you fall into the correct one
    931                 if (mHitStreak > 28) {
    932 
    933                     // did the bed change?
    934                     if (mCurrentBed != 7) {
    935                         // did it go up?
    936                         if (mCurrentBed < 7) {
    937                             mJet.triggerClip(7);
    938                         }
    939 
    940                         mCurrentBed = 7;
    941                         // JET info: change the mute mask to update the way the music plays based
    942                         // JET info: on the player's skills.
    943                         mJet.setMuteArray(muteMask[7], false);
    944 
    945                     }
    946                 } else if (mHitStreak > 24) {
    947                     if (mCurrentBed != 6) {
    948                         if (mCurrentBed < 6) {
    949                             // JET info: quite a few asteroids hit, trigger the clip with the guy's
    950                             // JET info: voice that encourages the player.
    951                             mJet.triggerClip(6);
    952                         }
    953 
    954                         mCurrentBed = 6;
    955                         mJet.setMuteArray(muteMask[6], false);
    956                     }
    957                 } else if (mHitStreak > 20) {
    958                     if (mCurrentBed != 5) {
    959                         if (mCurrentBed < 5) {
    960                             mJet.triggerClip(5);
    961                         }
    962 
    963                         mCurrentBed = 5;
    964                         mJet.setMuteArray(muteMask[5], false);
    965                     }
    966                 } else if (mHitStreak > 16) {
    967                     if (mCurrentBed != 4) {
    968 
    969                         if (mCurrentBed < 4) {
    970                             mJet.triggerClip(4);
    971                         }
    972                         mCurrentBed = 4;
    973                         mJet.setMuteArray(muteMask[4], false);
    974                     }
    975                 } else if (mHitStreak > 12) {
    976                     if (mCurrentBed != 3) {
    977                         if (mCurrentBed < 3) {
    978                             mJet.triggerClip(3);
    979                         }
    980                         mCurrentBed = 3;
    981                         mJet.setMuteArray(muteMask[3], false);
    982                     }
    983                 } else if (mHitStreak > 8) {
    984                     if (mCurrentBed != 2) {
    985                         if (mCurrentBed < 2) {
    986                             mJet.triggerClip(2);
    987                         }
    988 
    989                         mCurrentBed = 2;
    990                         mJet.setMuteArray(muteMask[2], false);
    991                     }
    992                 } else if (mHitStreak > 4) {
    993                     if (mCurrentBed != 1) {
    994 
    995                         if (mCurrentBed < 1) {
    996                             mJet.triggerClip(1);
    997                         }
    998 
    999                         mJet.setMuteArray(muteMask[1], false);
   1000 
   1001                         mCurrentBed = 1;
   1002                     }
   1003                 }
   1004             }
   1005         }
   1006 
   1007 
   1008         private void doAsteroidCreation() {
   1009             // Log.d(TAG, "asteroid created");
   1010 
   1011             Asteroid _as = new Asteroid();
   1012 
   1013             int drawIndex = mRandom.nextInt(4);
   1014 
   1015             // TODO Remove hard coded value
   1016             _as.mDrawY = mAsteroidMinY + (drawIndex * 63);
   1017 
   1018             _as.mDrawX = (mCanvasWidth - mAsteroids[0].getWidth());
   1019 
   1020             _as.mStartTime = System.currentTimeMillis();
   1021 
   1022             mDangerWillRobinson.add(_as);
   1023         }
   1024 
   1025 
   1026         /**
   1027          * Used to signal the thread whether it should be running or not.
   1028          * Passing true allows the thread to run; passing false will shut it
   1029          * down if it's already running. Calling start() after this was most
   1030          * recently called with false will result in an immediate shutdown.
   1031          *
   1032          * @param b true to run, false to shut down
   1033          */
   1034         public void setRunning(boolean b) {
   1035             mRun = b;
   1036 
   1037             if (mRun == false) {
   1038                 if (mTimerTask != null)
   1039                     mTimerTask.cancel();
   1040             }
   1041         }
   1042 
   1043 
   1044         /**
   1045          * returns the current int value of game state as defined by state
   1046          * tracking constants
   1047          *
   1048          * @return
   1049          */
   1050         public int getGameState() {
   1051             synchronized (mSurfaceHolder) {
   1052                 return mState;
   1053             }
   1054         }
   1055 
   1056 
   1057         /**
   1058          * Sets the game mode. That is, whether we are running, paused, in the
   1059          * failure state, in the victory state, etc.
   1060          *
   1061          * @see #setState(int, CharSequence)
   1062          * @param mode one of the STATE_* constants
   1063          */
   1064         public void setGameState(int mode) {
   1065             synchronized (mSurfaceHolder) {
   1066                 setGameState(mode, null);
   1067             }
   1068         }
   1069 
   1070 
   1071         /**
   1072          * Sets state based on input, optionally also passing in a text message.
   1073          *
   1074          * @param state
   1075          * @param message
   1076          */
   1077         public void setGameState(int state, CharSequence message) {
   1078 
   1079             synchronized (mSurfaceHolder) {
   1080 
   1081                 // change state if needed
   1082                 if (mState != state) {
   1083                     mState = state;
   1084                 }
   1085 
   1086                 if (mState == STATE_PLAY) {
   1087                     Resources res = mContext.getResources();
   1088                     mBackgroundImageFar = BitmapFactory
   1089                             .decodeResource(res, R.drawable.background_a);
   1090 
   1091                     // don't forget to resize the background image
   1092                     mBackgroundImageFar = Bitmap.createScaledBitmap(mBackgroundImageFar,
   1093                             mCanvasWidth * 2, mCanvasHeight, true);
   1094 
   1095                     mBackgroundImageNear = BitmapFactory.decodeResource(res,
   1096                             R.drawable.background_b);
   1097 
   1098                     // don't forget to resize the background image
   1099                     mBackgroundImageNear = Bitmap.createScaledBitmap(mBackgroundImageNear,
   1100                             mCanvasWidth * 2, mCanvasHeight, true);
   1101 
   1102                 } else if (mState == STATE_RUNNING) {
   1103                     // When we enter the running state we should clear any old
   1104                     // events in the queue
   1105                     mEventQueue.clear();
   1106 
   1107                     // And reset the key state so we don't think a button is pressed when it isn't
   1108                     mKeyContext = null;
   1109                 }
   1110 
   1111             }
   1112         }
   1113 
   1114 
   1115         /**
   1116          * Add key press input to the GameEvent queue
   1117          */
   1118         public boolean doKeyDown(int keyCode, KeyEvent msg) {
   1119             mEventQueue.add(new KeyGameEvent(keyCode, false, msg));
   1120 
   1121             return true;
   1122         }
   1123 
   1124 
   1125         /**
   1126          * Add key press input to the GameEvent queue
   1127          */
   1128         public boolean doKeyUp(int keyCode, KeyEvent msg) {
   1129             mEventQueue.add(new KeyGameEvent(keyCode, true, msg));
   1130 
   1131             return true;
   1132         }
   1133 
   1134 
   1135         /* Callback invoked when the surface dimensions change. */
   1136         public void setSurfaceSize(int width, int height) {
   1137             // synchronized to make sure these all change atomically
   1138             synchronized (mSurfaceHolder) {
   1139                 mCanvasWidth = width;
   1140                 mCanvasHeight = height;
   1141 
   1142                 // don't forget to resize the background image
   1143                 mBackgroundImageFar = Bitmap.createScaledBitmap(mBackgroundImageFar, width * 2,
   1144                         height, true);
   1145 
   1146                 // don't forget to resize the background image
   1147                 mBackgroundImageNear = Bitmap.createScaledBitmap(mBackgroundImageNear, width * 2,
   1148                         height, true);
   1149             }
   1150         }
   1151 
   1152 
   1153         /**
   1154          * Pauses the physics update & animation.
   1155          */
   1156         public void pause() {
   1157             synchronized (mSurfaceHolder) {
   1158                 if (mState == STATE_RUNNING)
   1159                     setGameState(STATE_PAUSE);
   1160                 if (mTimerTask != null) {
   1161                     mTimerTask.cancel();
   1162                 }
   1163 
   1164                 if (mJet != null) {
   1165                     mJet.pause();
   1166                 }
   1167             }
   1168         }
   1169 
   1170 
   1171         /**
   1172          * Does the work of updating timer
   1173          *
   1174          */
   1175         private void doCountDown() {
   1176             //Log.d(TAG,"Time left is " + mTimerLimit);
   1177 
   1178             mTimerLimit = mTimerLimit - 1;
   1179             try {
   1180                 //subtract one minute and see what the result is.
   1181                 int moreThanMinute = mTimerLimit - 60;
   1182 
   1183                 if (moreThanMinute >= 0) {
   1184 
   1185                     if (moreThanMinute > 9) {
   1186                         mTimerValue = "1:" + moreThanMinute;
   1187 
   1188                     }
   1189                     //need an extra '0' for formatting
   1190                     else {
   1191                         mTimerValue = "1:0" + moreThanMinute;
   1192                     }
   1193                 } else {
   1194                     if (mTimerLimit > 9) {
   1195                         mTimerValue = "0:" + mTimerLimit;
   1196                     } else {
   1197                         mTimerValue = "0:0" + mTimerLimit;
   1198                     }
   1199                 }
   1200             } catch (Exception e1) {
   1201                 Log.e(TAG, "doCountDown threw " + e1.toString());
   1202             }
   1203 
   1204             Message msg = mHandler.obtainMessage();
   1205 
   1206             Bundle b = new Bundle();
   1207             b.putString("text", mTimerValue);
   1208 
   1209             //time's up
   1210             if (mTimerLimit == 0) {
   1211                 b.putString("STATE_LOSE", "" + STATE_LOSE);
   1212                 mTimerTask = null;
   1213 
   1214                 mState = STATE_LOSE;
   1215 
   1216             } else {
   1217 
   1218                 mTimerTask = new TimerTask() {
   1219                     public void run() {
   1220                         doCountDown();
   1221                     }
   1222                 };
   1223 
   1224                 mTimer.schedule(mTimerTask, mTaskIntervalInMillis);
   1225             }
   1226 
   1227             //this is how we send data back up to the main JetBoyView thread.
   1228             //if you look in constructor of JetBoyView you will see code for
   1229             //Handling of messages. This is borrowed directly from lunar lander.
   1230             //Thanks again!
   1231             msg.setData(b);
   1232             mHandler.sendMessage(msg);
   1233 
   1234         }
   1235 
   1236 
   1237         // JET info: JET event listener interface implementation:
   1238         /**
   1239          * required OnJetEventListener method. Notifications for queue updates
   1240          *
   1241          * @param player
   1242          * @param nbSegments
   1243          */
   1244         public void onJetNumQueuedSegmentUpdate(JetPlayer player, int nbSegments) {
   1245             //Log.i(TAG, "onJetNumQueuedUpdate(): nbSegs =" + nbSegments);
   1246 
   1247         }
   1248 
   1249 
   1250         // JET info: JET event listener interface implementation:
   1251         /**
   1252          * The method which receives notification from event listener.
   1253          * This is where we queue up events 80 and 82.
   1254          *
   1255          * Most of this data passed is unneeded for JetBoy logic but shown
   1256          * for code sample completeness.
   1257          *
   1258          * @param player
   1259          * @param segment
   1260          * @param track
   1261          * @param channel
   1262          * @param controller
   1263          * @param value
   1264          */
   1265         public void onJetEvent(JetPlayer player, short segment, byte track, byte channel,
   1266                 byte controller, byte value) {
   1267 
   1268             //Log.d(TAG, "jet got event " + value);
   1269 
   1270             //events fire outside the animation thread. This can cause timing issues.
   1271             //put in queue for processing by animation thread.
   1272             mEventQueue.add(new JetGameEvent(player, segment, track, channel, controller, value));
   1273         }
   1274 
   1275 
   1276         // JET info: JET event listener interface implementation:
   1277         public void onJetPauseUpdate(JetPlayer player, int paused) {
   1278             //Log.i(TAG, "onJetPauseUpdate(): paused =" + paused);
   1279 
   1280         }
   1281 
   1282         // JET info: JET event listener interface implementation:
   1283         public void onJetUserIdUpdate(JetPlayer player, int userId, int repeatCount) {
   1284             //Log.i(TAG, "onJetUserIdUpdate(): userId =" + userId + " repeatCount=" + repeatCount);
   1285 
   1286         }
   1287 
   1288     }//end thread class
   1289 
   1290     public static final String TAG = "JetBoy";
   1291 
   1292     /** The thread that actually draws the animation */
   1293     private JetBoyThread thread;
   1294 
   1295     private TextView mTimerView;
   1296 
   1297     private Button mButtonRetry;
   1298 
   1299     // private Button mButtonRestart;
   1300     private TextView mTextView;
   1301 
   1302     /**
   1303      * The constructor called from the main JetBoy activity
   1304      *
   1305      * @param context
   1306      * @param attrs
   1307      */
   1308     public JetBoyView(Context context, AttributeSet attrs) {
   1309         super(context, attrs);
   1310 
   1311         // register our interest in hearing about changes to our surface
   1312         SurfaceHolder holder = getHolder();
   1313         holder.addCallback(this);
   1314 
   1315         // create thread only; it's started in surfaceCreated()
   1316         // except if used in the layout editor.
   1317         if (isInEditMode() == false) {
   1318             thread = new JetBoyThread(holder, context, new Handler() {
   1319 
   1320                 public void handleMessage(Message m) {
   1321 
   1322                     mTimerView.setText(m.getData().getString("text"));
   1323 
   1324                     if (m.getData().getString("STATE_LOSE") != null) {
   1325                         //mButtonRestart.setVisibility(View.VISIBLE);
   1326                         mButtonRetry.setVisibility(View.VISIBLE);
   1327 
   1328                         mTimerView.setVisibility(View.INVISIBLE);
   1329 
   1330                         mTextView.setVisibility(View.VISIBLE);
   1331 
   1332                         Log.d(TAG, "the total was " + mHitTotal);
   1333 
   1334                         if (mHitTotal >= mSuccessThreshold) {
   1335                             mTextView.setText(R.string.winText);
   1336                         } else {
   1337                             mTextView.setText("Sorry, You Lose! You got " + mHitTotal
   1338                                     + ". You need 50 to win.");
   1339                         }
   1340 
   1341                         mTimerView.setText("1:12");
   1342                         mTextView.setHeight(20);
   1343 
   1344                     }
   1345                 }//end handle msg
   1346             });
   1347         }
   1348 
   1349         setFocusable(true); // make sure we get key events
   1350 
   1351         Log.d(TAG, "@@@ done creating view!");
   1352     }
   1353 
   1354 
   1355     /**
   1356      * Pass in a reference to the timer view widget so we can update it from here.
   1357      *
   1358      * @param tv
   1359      */
   1360     public void setTimerView(TextView tv) {
   1361         mTimerView = tv;
   1362     }
   1363 
   1364 
   1365     /**
   1366      * Standard window-focus override. Notice focus lost so we can pause on
   1367      * focus lost. e.g. user switches to take a call.
   1368      */
   1369     @Override
   1370     public void onWindowFocusChanged(boolean hasWindowFocus) {
   1371         if (!hasWindowFocus) {
   1372             if (thread != null)
   1373                 thread.pause();
   1374 
   1375         }
   1376     }
   1377 
   1378 
   1379     /**
   1380      * Fetches the animation thread corresponding to this LunarView.
   1381      *
   1382      * @return the animation thread
   1383      */
   1384     public JetBoyThread getThread() {
   1385         return thread;
   1386     }
   1387 
   1388 
   1389     /* Callback invoked when the surface dimensions change. */
   1390     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
   1391         thread.setSurfaceSize(width, height);
   1392     }
   1393 
   1394 
   1395     public void surfaceCreated(SurfaceHolder arg0) {
   1396         // start the thread here so that we don't busy-wait in run()
   1397         // waiting for the surface to be created
   1398         thread.setRunning(true);
   1399         thread.start();
   1400     }
   1401 
   1402 
   1403     public void surfaceDestroyed(SurfaceHolder arg0) {
   1404         boolean retry = true;
   1405         thread.setRunning(false);
   1406         while (retry) {
   1407             try {
   1408                 thread.join();
   1409                 retry = false;
   1410 
   1411             } catch (InterruptedException e) {
   1412             }
   1413         }
   1414     }
   1415 
   1416 
   1417     /**
   1418      * A reference to the button to start game over.
   1419      *
   1420      * @param _buttonRetry
   1421      *
   1422      */
   1423     public void SetButtonView(Button _buttonRetry) {
   1424         mButtonRetry = _buttonRetry;
   1425         //  mButtonRestart = _buttonRestart;
   1426     }
   1427 
   1428 
   1429     //we reuse the help screen from the end game screen.
   1430     public void SetTextView(TextView textView) {
   1431         mTextView = textView;
   1432 
   1433     }
   1434 }
   1435