Now we’ve finished describing most of the initialisation methods, setMode() is worth a look, as the main Activity calls that. Depending on if it’s send READY, PAUSED etc it will update the SnakeView’s TextView with the appopriate info. If it’s send RUNNING, and it wasn’t before, it will hide the TextView and update the screen. It’s send RUNNING by the onKeyPress(), when the user pressed the UP DPAD view when the state is READY (set by the Activity) or LOSE (set by SnakeView).
onKeyDown() sets the mNextDirection based on the DPAD values. If UP is pressed, and the state is READY or LOSE, then it runs initNewGame(), sets the mode to RUNNING, and updates the screen. initNewGame() wasn’t described before. But it merely sets up the snake with new coords in its ArrayList sets some random apples via addRandomApple(), and sets our score to zero and our mMoveDelay to 600.
The addRandomApple() finds a random X and Y position, via the Random class defined previously, then loops over the ArrayList of the snake body, and sets a collision value to true if there’s hit. If there is hit, it does it over again until there’s no collision. Then it adds that to the ArrayList of apples. The updateApples() method goes over that list and add a tile to that position, using setTile() is the super class.
The updateWalls() looks at the mTileCountX and Y values, and loops through to draw a border around the screen. Those two values are defined in the super class.
The update() method simply checks if the state is RUNNING, looks at the current time, and minuses the mLastMove value, and checks that’s over the mMoveDelay value. If so, it runs clearTiles() in the super class. updateWalls(), updateApples(), updateSnake() (detailed below) and sets mLastMove to the current time. Regardless, mRedrawHandler.sleep() is called to redraw the screen—via invalidate() and to call update() again, thereby causing the display loop—after waiting mMoveDelay miliseconds.
The updateSnake() method is the most complex. It gets the snakes head position. Then based on the mDirection, which is now set a mNextDirection, add a new coordinate to the snake’s arraylist. Then check the new head isn’t either touching a side or the snake. If it is set the mode to LOSE and return.
If the new head hits an apple, increase the score and call addRandomApple(). And then decrease the mMoveDelay so the snake goes faster. Then set growSnake to true. If it’s not true, remove the tail of the snake, to simulate movement. Then use the super class’s setTile function to set all the tiles for the snake. The same function that drew the walls and apples.
And that’s that. Now time to look at the super class, which had the all important setTile() method.
Now we’ve looked at its large number of variables, here are the constructors and boring utility methods.
The two constructor methods simply pass the Context and the AttributeSet and defStyle from the XML declaration to its super class—the TileView which we’ll look at later. Both of the constructors call initSnakeView().
initSnakeView() sets this View to focused. It calls up the resources for this app. Gets the drawables for the border and snake etc. Loads those as the RED_STAR, YELLOW_STAR etc variables using the super class’s loadTile(). The method also resets the Bitmaps used in the TileView using resetTiles().
The coordArrayListToArray() sets our ArrayList of snake location or apples list to an array of ints. This is so it can be placed into a Bundle later. coordArrayToArrayList() is its reverse. The first is used in saveState() where the applelist, snakelist, direction, score etc is put into a Bundle. restoreState() takes a Bundle filled with those values and restores the global values with such.
setViewText() simply sets a TextView to the mStatusText variable so this class can output its text somewhere.
The Coordinate class, previously described, is simply a class with an x and y value. Its equals() methods takes in another Coordinate and says if they’re equal or not.
Now all the bording methods and classes are done, on to the real logic of the class.
The class starts off by defining various static int used to reference various states, some arrays to store the snake location and various other bits.
class RefreshHandler extends Handler {
@Override
public void handleMessage(Message msg) {
SnakeView.this.update();
SnakeView.this.invalidate();
}public void sleep(long delayMillis) {
this.removeMessages(0);
sendMessageDelayed(obtainMessage(0), delayMillis);
}
};
This is only used to sleep a little while then update the SnakeView. The threaded part of Handler isn’t used in Snake.
Now the declarations are over, onto the meat/tofu of the class.
This series is going to be detailing the Snake SDK example in the Android docs. More for my edification than anything else. Hope it helps someone.
In its onCreate() method it finds a custom view, a SnakeView, from the layout. And then gives that object a reference to a TextView from the layout. That will be used to display game info.
Then if there’s no savedInstanceState Bundle, set SaneView to READY mode (we’ll describe that method later). Otherwise it gets that Bundle and gives it to the SnakeView to restore.
If there is a Bundle, but from that it doesn’t find another “snake-view” Bundle in the first Bundle, it sets the SnakeView to paused. Not sure why there’d ever be a Bundle that onSaveInstanceState() didn’t put there.
if (savedInstanceState == null) {
// We were just launched -- set up a new game
mSnakeView.setMode(SnakeView.READY);
} else {
// We are being restored
Bundle map = savedInstanceState.getBundle(ICICLE_KEY);
if (map != null) {
mSnakeView.restoreState(map);
} else {
mSnakeView.setMode(SnakeView.PAUSE);
}
}
The onPause() activity method tells the SnakeView to pause. Its onSaveInstanceState() puts the SnakeView state Bundle into the outState Bundle.
We can use Android’s speech recognition to return a list of possibilities of what was spoken.
First we need to make sure the system has this capability. Most should. The emulator doesn’t.
PackageManager pm = getPackageManager();
List<ResolveInfo> activities = pm.queryIntentActivities(
new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0);
if (activities.size() != 0) {
// do stuff. see below
} else {
Toast.makeText(this, “shitters”, Toast.LENGTH_LONG).show();
}
First we the PackageManager’s queryIntentActivities() method to get a List of ResolveInfos that relate to the Intent ACTION_RECOGNIZE_SPEECH.
If we have zero responses, the device doesn’t have the capability, otherwise we can run some code. The code isn’t above for brevity, but it’s below:
Intent i = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
i.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
i.putExtra(RecognizerIntent.EXTRA_PROMPT, “Speak up son!”);
startActivityForResult(i, 111);
We start an Intent, giving it a ACTION_RECOGNISE_SPEECH type.
And we must, to recognise speech, give it an extra, name EXTRA_LANGUAGE_MODEL as LANGUAGE_MODEL_FREE_FORM. The language model is what kind of language we’ll be looking out for, and free form means normal speech. The other option currently is WEB_SEARCH for web terms.
Then we give it a prompt that will appear when the Intent is shown, and finally we run the activity for a result. So we need to catch it in onActivityResult():
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode==111 && resultCode==RESULT_OK) {
ArrayList<String> res = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
// do something with the results
}
}
We first look for the request code we defined above, and then check the result is okay. From within the Intent that the speech recognition intent passes back, we can get back an ArrayList with the key RecognizerIntent.EXTRA_RESULTS.
This ArrayList has a list of all the possibly words or phrases that user could have said.
You can define a horizontal seek bar fairly easily. Vertical is not yet supported. There are few attempts at it on the net though.
<SeekBar android:layout_height=”wrap_content”
android:indeterminate=”false”
android:thumb=”@drawable/icon”
android:id=”@+id/seekBar1”
android:layout_width=”match_parent”>
</SeekBar>
The XML’s attributes are standard except indeterminate as true just sets the seekbar to repeat on its own. The ‘tumb’ lets you define a customisable icon.
SeekBar sb = (SeekBar) findViewById(R.id.seekBar1);
sb.setMax(max);
sb.setProgress(curr);
The sexMax() and setProgress() methods should be self explanatory. It also takes a lister to relay its information to the program:
sb.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
//do something with the progress value
}
});
The onProgressChanged() method is the one you’ll probably want. Although the others are useful if you want do something on the start or the end of a seek.
Within the onProgressChanged() method you’ll have access to the current value of the bar and whether it was programmatically changed or not.
This manager allows you to set the volume for various sound types. We’ll set the music in this case.
First get an AudioManger from the system.
AudioManager am = (AudioManager) getSystemService(AUDIO_SERVICE);
This has various methods, such as getting the max and current volume:
int max = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
int curr = am.getStreamVolume(AudioManager.STREAM_MUSIC);
Note we’re passing it a STREAM_MUSIC parameter. There are other streams we can access such as ALARM, NOTIFICATION etc.
To set the volume:
am.setStreamVolume(AudioManager.STREAM_MUSIC, 10, 0);
Note we’re specifying a stream type again and passing in an int. That much not be above the max volume, as found above.
Note you obviously have to have some sound playing. Either using the default music player, or via this.
The first thing you need to do in your onCreate() is check the phone needs to download support. The emulator didn’t, but my phone had to. You do this by calling an Intent which returns to you whether all went well or not.
Intent checkIntent = new Intent();
checkIntent.setAction(TextToSpeech.Engine.ACTION_CHECK_TTS_DATA);
startActivityForResult(checkIntent, 1001);
This will return an Intent which we must catch:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode==1001 &&
resultCode!=TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
installTTSPack();
} else if(requestCode==1 &&
resultCode==TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) {
initTTS();
}
}
If the result of the Intent says it does have the voice data, then initTTS(), which we’ll define in a moment. If it doesn’t have it, then installTTSPack():
private void installTTSPack() {
Intent installIntent = new Intent();
installIntent.setAction(
TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA);
startActivity(installIntent);
}
This starts up an Intent that takes the user to the Marketplace. From there they can install the TTS language pack.
Note that there is no way to determine whether this has been successful or not other than the Intent right at the start. Once it’s successful—which you can check by running the first Intent again or on initalisation of the TTS below—it’s wise to set a boolean stating this, then your actual methods that output speech can only run when this is true, and run the install intent again when it’s not.
private void initTTS() {
if(tts==null)
tts = new TextToSpeech(this, new TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
if(status!= TextToSpeech.ERROR) {
tts.setLanguage(Locale.ENGLISH);
//now do some tts methods or enable a bool saying we can
}
}
});
}
}
This methods makes us a new TextToSpeech() object, passing the current Context and a callback that is called when the engine is initialised. If there’s not been an error, we set the TTS language. Note that you should use the tts.isLanguageAvailable() method before setting the language.
Finally, once that’s been loaded we can run:
tts.speak(“Some arbitrary text for you”, TextToSpeech.QUEUE_FLUSH, null);
The QUEUE_FLUSH means stop whatever was previously playing.
Note that, since we didn’t know if the language pack has been installed (the user could have decided to press back instead of install, or it may have failed due to lack of space on the SDCard), you should disable any speech until you’re certain it’s installed. And run the first Intent again if you’re not.
Although our previous 2D graphics method worked, as it’s using the View system, it can be a little slow depending on what you’re doing. The SurfaceView gives us direct access to the 2D surface.
First define a class that extends the SurfaceView. And make it implement Runnable, as the drawing method will run in its own Thread:
public class GraphicClass extends SurfaceView implements Runnable
private SurfaceHolder mSurfaceHolder;
private Thread mThread;
private boolean mIsRunning = true;public GraphicClass(Context c) {
super(c);
mSurfaceHolder = getHolder();
}
We’re declaring a SurfaceHolder, which will give us access to the graphics surface. Then a Thread which we will use to draw to the screen. And finally a boolean to specify if the thread should run or not.
Our constructor simply gets a SurfaceHolder for us, and passes a Context to the class.
public void pause() {
mIsRunning=false;while(true) {
try {
mThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
break;
}
mThread=null;
}public void resume() {
mIsRunning = true;
mThread = new Thread(this);
mThread.start();
}
The pause() method first sets mIsRunning to false. This boolean is used in our thread’s run() method—soon to be defined—so it loops, repeatedly drawing our graphics. When it’s null, the thread finished its execution. We then use .join() on our thread to wait until the execution is finished. Then the thread is set to null.
resume() method sets our boolean to true, meaning the Thread’s run() method will loop when it’s started, repeatedly drawing our graphics. Then we define and start a new thread—passing it ‘this’, as ‘this’ will have run() method in it shortly:
@Override
public void run() {
while(mIsRunning) {
if(!mSurfaceHolder.getSurface().isValid())
continue;
Canvas canvas = mSurfaceHolder.lockCanvas();canvas.drawRGB(9, 109, 9);
Paint p = new Paint();
p.setColor(Color.BLUE);
canvas.drawRect(new Rect(100, 100, 200, 200), p);
mSurfaceHolder.unlockCanvasAndPost(canvas);}
}
Now the run method. Note the mIsRunning boolean that was set in the resume() and pause() methods to indicate where this Thread’s run() method should loop or finish.
Within that loop we check if the surface is in a good state. If it’s not we just skip this loop. Then we get a lock on the canvas. Note that is all ends by unlocking the canvas and drawing the canvas.
Within those two methods we can do whatever we want with the canvas. Drawing bitmaps, rectangles, clipping regions, defining paint objects etc. We’re just drawing a background color and rectangle.
}
Now we’ve finished our SurfaceView class.we need to tell our Activity to render it. In our onCreate() method call onContentView() not with a reference to a layout object, but this SurfaceView instead.
And in our Activity’s onPause and onResume() methods, call the above class’s pause() and resume() methods so the Thread isn’t running while the Activity is paused/killed.
(This follows on from this set).
You can make an Activity appear when the user goes to add your Widget to their screen for configuration purposes.
First say you’ll be creating a configuration Activity in your manifest XML file:
<activity android:name=”.WidgetConfig”>
<intent-filter>
<action android:name=”android.appwidget.action.APPWIDGET_CONFIGURE” />
</intent-filter>
</activity>
Note that this insinuates we’ll be breating a WidgetConfig activity later. The intent-filter is there because the system will send out a APPWIDGET_CONFIGURATION Intent, so we need to listen for that.
Next in your <appwidget-provider> xml file we previously created, add a new ‘configuration’ attribute with the full class path of our configuration activity:
<?xml version=”1.0” encoding=”utf-8”?>
<appwidget-provider
xmlns:android=”http://schemas.android.com/apk/res/android”
android:minWidth=”79px”
android:minHeight=”79px”
android:updatePeriodMillis=”5000”
android:initialLayout=”@layout/widget”
android:configure=”YOUR.PATH.WidgetConfig”
/>
Next create that WidgetConfig class. We’ll put everything in the constructor for the sake of brevity:
public class WidgetConfig extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.widgetconfig);Intent i = getIntent();
Bundle extra = i.getExtras();
int awid=0;
if(extra!=null) {
awid = extra.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
} else {
finish();
}
We’re first getting a the Bundle from the Intent that invoked this Activity. Android will have sent that. It will have put the id of the widget in the EXTRA_APPWIDGET_ID param. We’ll use that ID to update the widget.
Note we’re setting a layout we haven’t defined yet. It’s just a usual layout. And it won’t be used subsequently. Although you would use it to grab the configuration data that you’ll use the configure the widget—grabbing strings from edittexts and placing them on your widget via a RemoteViews etc, as we did previously with widgets.
AppWidgetManager awm = AppWidgetManager.getInstance(cx);
Next we get the AppWidgetManager for this class. We’d use its updateAppWidget(widgetID, RemoteViews) method as before, but this time with data from the Configuration Activity’s layout.
Intent res = new Intent();
res.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, awid);
setResult(RESULT_OK, res);
finish();
Next we’ll make an Intent will the widget ID found above, and return that to the system with the RESULT_OK Intent result. Finally we’ll finish, as we’ve all configured our widget.
}
}
Note that, if you use a Configuration Activity for your widget, onUpdate() in your BroadcastReceiver will not be called on startup—but it will be called every 30 minutes or so as usual. So you need to put all your initialisation stuff in your configuration activity instead.
Unlike the simple JSON parsing function (you just put a string of JSON in a JSONArray object), XML is a little more involved.
URL url = new URL(“http://www.google.com/ig/api?weather=Paris,France”);
InputSource is = new InputSource(url.openStream());
is.setEncoding(“ISO-8859-1”);
We first get a usual URL, and then put it into a SAX InputSource. We set its encoding to ISO-8859-1 as the google weather XML for Paris is in that format. SAX will freak if we don’t do this.
SAXParser sax = SAXParserFactory.newInstance().newSAXParser();
XMLReader xr = sax.getXMLReader();
xr.setContentHandler(new XMLHandler());
Then we get a new SAXParser object from the SAXParserFactory’s newInstance().newSAXParser() methods. And get that’s XMLReader.
Finally we give the XMLReader a new ContentHandler—which we’ll define below.
xr.parse(is);
Lastly, we give the InputSource defined above to the XMLReader.
Now for the ContentHandler derives DefaulHandler class:
private class XMLHandler extends DefaultHandler {
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
if(localName.equals(“city”)) {
String city=attributes.getValue(“data”);
} else if(localName.equals(“temp_c”)) {
String temp=attributes.getValue(“data”);
}
}
}
The startElement() takes in a the tag name of each tag is finds in the XML file. We’re seeing if that equals either <city> or <temp_c>. If it does, then we want the attribute value of that XML tag, ‘data’ in this instance. We’re just setting that as a useless String var here. We could obviously put this to a TextView or such somewhere.
Check out the XML file we’re parsing here.
We previously found the home location using the MyLocationOverlay class, and we can even listen on that to get a new location.
There’s another way, however, and this one gives you access to the Provider information, including the ability to specify the accuracy of provider information.
lm = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
Criteria c = new Criteria();
c.setAccuracy(Criteria.ACCURACY_FINE);
towers = lm.getBestProvider(c, false);
We first get the location manager. Then specify some critera—we’re saying we only want fine grained information. Then we’re getting a name of a provider using the Criteria object—specify simply new Criteria for no criteria. This is either gps, network or passive.
if(towers!=null) {
lm.requestLocationUpdates(towers, 500, 1,
new LocationListener() {
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
@Override
public void onProviderEnabled(String provider) {}
@Override
public void onProviderDisabled(String provider) {}
@Override
public void onLocationChanged(Location location) {
GeoPoint point = new GeoPoint((int)(location.getLatitude()*1E6),
(int)(location.getLongitude()*1E6));
}
});
}
Finally we’re requsting a location update using the towers String, every 500 miliseconds and 1 meter minimum distance. We pass it a reference to a LocationListener. We’re only using this as an inner class here for briefity. Yes, it has too many methods for a inner class really.
We’re only using the passed location to grab a new GeoPoint. What you do with that then, such a animating to that point using a MapController etc, is up to you.
Remember, you need these permissions on the AndroidManifest.xml:
<uses-permission android:name=”android.permission.INTERNET”/>
<uses-permission android:name=”android.permission.ACCESS_FINE_LOCATION”/>
You may want ACCESS_COARSE_LOCATION instead. Although you’ll have to change the criteria above, as you’re currently asking for FINE.
Also note, it’s possible that getBestProvider() may equal null if the permissions aren’t correct or the criteria doesn’t, so check that before you start using it, else you’ll get exceptions thrown. And consider using getAllProviders() to make a backup option.
Run this on the device, not the emulator.
Once you’ve got your MapView, it’s easy to switch between street and satellite view:
if(mv.isSatellite()) {
mv.setSatellite(false);
mv.setStreetView(true);
} else {
mv.setSatellite(true);
mv.setStreetView(false);
}
This simply switches to whatever not set currently.
If we want to do something when a user clicks on a map, we need to define our own overlay:
class OurOverlay extends Overlay {
@Override
public boolean onTouchEvent(MotionEvent e, MapView mapView) {}
}
This just extends an Overlay class and implements the onTouchEvent() that will be called on each click. We’d add that overlay to our MapView as we’ve done with the other overlays we’ve dealt with.
To expand the onTouchEvent() method a little more.
We start by waiting until the use presses down and then gets the X and Y of the mouse event, passing them to a Projection from our MapView, and using the fromPixels() method of that to get the real GPS point.
public boolean onTouchEvent(MotionEvent e, MapView mapView) {
if(e.getAction()==MotionEvent.ACTION_DOWN) {
Geopoint mGp = mv.getProjection()
.fromPixels((int) e.getX(), (int) e.getY());Geocoder geocode = new Geocoder(getBaseContext(),
Locale.getDefault());
try {
List<Address> address =
geocode.getFromLocation(
mGp.getLatitudeE6()/1E6,
mGp.getLongitudeE6()/1E6, 1);
if(address.size()>0) address.get(0).getCountyName(); // or whatever
} catch (IOException ee) {
ee.printStackTrace();
}}
return super.onTouchEvent(e, mapView);
}
We then get a Geocoder that will translate our coordinates into information about the location. It’s based the context of the app, and a locale—the default one here.
Then we use that Geocoder to get addresses from the Geopoint we found earlier, using the getFromLocation() method. We pass the latitude and longitute. We divide by 1E6 as that function requires the values to be doubles, not integers as they are currently. And we only get ‘1’ address.
From there you can check there’s one that one item in that Address list and play with it’s various methods such as getCountryName(), getPhone(), getAddressLine(i) etc.
Note that you should use Geocode.isPresent() to check if your phone actually does have a Geocoder backend present—it’s not shipped by default according to the docs. And the emulate doesn’t support this—it’s a bug. getFromLocation() will throw a IOException. The normal device should work though.
To animate and zoom to a point on the map, we need a MapController.
We’ll use the coordinates of our previous MyLocationOveraly to zoom and animate to that point. Make sure this variable is now ‘final’ as we’ll be using from inside an inner class:
final MapController mController = mv.getController();
myLocationOverlay.runOnFirstFix(new Runnable() {public void run() {
mController.animateTo(myLocationOverlay.getMyLocation());
mController.setZoom(3);
}
});
Firstly we get a controller via the MapView’s getController() method. Then we use MyLocationOverlay’s runOnFirstFix() method that runs when the location is first set—when we set it from DDMS.
In the run() method we’re using the controller to animate to the location of MyLocationOverlay. Then we’re zooming in.