Monday 31 May 2010

Apple iPhone EarPhone Controls - Cheat Sheet

I recently got the fancy Apple earphones for my iPhone GS. Along with the microphone, the earphones include a volume control. But wait ... there's more!!!!



In addition to volume control, you can now skip, fast forward, and rewind all from your headphones. No need to get your iPhone or iPod out of your pocket. Unfortunately, the command listing comes in a thick little booklet that contains about 20 language translations. Not very portable or useful. Therefore, I created the following english cheat sheet and posted it on this site. The page includes a link which allows you to store the cheat sheet offline in a bookmark. Enjoy!



iPhone/iPod Earphone Commands

Sunday 30 May 2010

CSS Tip: Remove Part of a Border

CSS ButtonOften when working with a box thingy, you want a border on 3 sides, but not four. Normally, you would code that with something like this:



.someBoxyThing{

border-top:1pt solid black;

border-right:1pt solid black;

border-left:1pt solid black;

}




Instead of doing this, create a complete border, then eliminate one side. For example:



.someBoxyThing{

border:1pt solid black;

border-bottom:0px;

}



Now you have a three sided box with the bottom side eliminated.  Nice. Shout out to websiteoptimization.com for the tip.

Another exercise of SurfaceView, in a FrameLaout inside another LinearLayout

It's another exercise of using FurfaceView in Android.

In the previous exercises "Android SurfaceView" and "Android SurfaceView, run in Thread with sleep()", the layout was simple a FrameLayout composed of a SurfaceView only, setup using programming code setContentView(R.layout.main).

In this exercise, it's a FrameLayout inside a LinearLayout, together with two buttons. The FrameLayout is composed of a SurfaceView. And the layout is defined in main.xml. Both the MySurfaceView(extends SurfaceView) and the MySurfaceThread(extends Thread) are implemented as separated class. When the application is running, there are two buttons over the FrameLayout with SurfaceView, a dot bounce inside the SurfaceView. User can click the first button to make the another button invisible, to change the dimension of the FrameLayout in run-time, and the SurfaceView change also accordingly.



main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<Button
android:id="@+id/showhide"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Toggle The Another Button Show/Hide" />
<Button
android:id="@+id/dummy"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="a Button" />
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<com.exercise.AndroidMergeSurfaceView.MySurfaceView
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</FrameLayout>
</LinearLayout>


MySurfaceView.java
package com.exercise.AndroidMergeSurfaceView;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback{

private MySurfaceThread thread;
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
int cx, cy, offx, offy;

public MySurfaceView(Context context) {
super(context);
// TODO Auto-generated constructor stub
init();
}

public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
init();
}

public MySurfaceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
init();
}

private void init(){
getHolder().addCallback(this);
thread = new MySurfaceThread(getHolder(), this);

setFocusable(true); // make sure we get key events

paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
paint.setColor(Color.WHITE);

cx = 0;
cy = 0;
offx = 10;
offy = 10;

}

@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub

}

@Override
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
thread.setRunning(true);
thread.start();

}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
}
catch (InterruptedException e) {
}
}
}

@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
canvas.drawRGB(0, 0, 0);
canvas.drawCircle(cx, cy, 3, paint);
cx += offx;
if (cx > getWidth() || (cx < 0)){
offx *= -1;
cx += offx;
}

cy += offy;
if (cy > getHeight() || (cy < 0)){
offy *= -1;
cy += offy;
}
}
}


MySurfaceThread.java
package com.exercise.AndroidMergeSurfaceView;

import android.graphics.Canvas;
import android.view.SurfaceHolder;

public class MySurfaceThread extends Thread {
private SurfaceHolder myThreadSurfaceHolder;
private MySurfaceView myThreadSurfaceView;
private boolean myThreadRun = false;

public MySurfaceThread(SurfaceHolder surfaceHolder, MySurfaceView surfaceView) {
myThreadSurfaceHolder = surfaceHolder;
myThreadSurfaceView = surfaceView;
}

public void setRunning(boolean b) {
myThreadRun = b;
}

@Override
public void run() {
// TODO Auto-generated method stub
while(myThreadRun){
Canvas c = null;

try{
c = myThreadSurfaceHolder.lockCanvas(null);
synchronized (myThreadSurfaceHolder){
myThreadSurfaceView.onDraw(c);
}
sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
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) {
myThreadSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}


AndroidMergeSurfaceView.java
package com.exercise.AndroidMergeSurfaceView;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

public class AndroidMergeSurfaceView extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

Button buttonShowHide = (Button)findViewById(R.id.showhide);
final Button buttonDummy = (Button)findViewById(R.id.dummy);

buttonShowHide.setOnClickListener(
new Button.OnClickListener(){

@Override
public void onClick(View arg0) {
// TODO Auto-generated method stub
if(buttonDummy.getVisibility()==View.VISIBLE){
buttonDummy.setVisibility(View.GONE);
}
else{
buttonDummy.setVisibility(View.VISIBLE);
}
}

}
);

}
}


Download the files.

Further exercise on SurfaceView: SurfaceView overlap with a LinearLayout









It's a bug here!


Pls. refer to the article
"IllegalThreadStateException in LunarLander"
for details.


I'm Sorry about that! ()



Wednesday 26 May 2010

iPad: SimpleNote is the Note App You are Looking For

Ever since I got an iPhone, I have been looking for a replacement for Notes apps. The app only syncs with Mac Mail or Outlook, neither of which I use. What I need is a notes app that can sync to the Net. The app should allow me to add notes from any desktop machine, notebook, phone, or iPad and sync them to all the devices.



Well SimpleNote does that, and it does it very well. Nothing fancy about this app. You just get a box you can type in. The first line is the title, the rest just text.



On the iPad, SimpleNote looks something like this:



The title of your notes on the left. And the text associated with the selected note on the right.



In the iPhone, the interface is simpler. Just click on the note title brings up the note. Here is a snapshot.



The syncing is truly amazing. Just bring up SimpleNote on whatever platform you are using, bing, you have the latest and greatest notes. This is an app everyone should have.

Tuesday 25 May 2010

Two view in one FrameLayout

In my previous exercise "Draw a bitmap on View", "Draw something on a Canvas", "Android FaceDetector" and "Custom View with User Interaction", everything is drawn on ONE view. In this exercise, it will be separated in two views. The un-changed bitmap is drawn on View1, and user interaction is drawn on View2. Such that, everytime VIew2.onDraw() will redraw the interactive part only, no need to re-draw the bitmap.

Two view in one FrameLayout

The layout file, main.xml, is modified to use a FrameLayout with two View, cover whole of the screen.

- place a bitmap of 320x480 in /res/drawable/ folder, or using the bitmap in the downloaded files.

- Create two view class extends View

View1.java
package com.exercise.AndroidTwoView;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;

public class View1 extends View {

public View1(Context context) {
super(context);
// TODO Auto-generated constructor stub
}

public View1(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}

public View1(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}

@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
Bitmap myBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.googlelogo320x480);
canvas.drawBitmap(myBitmap, 0, 0, null);

}

}


View2.java
package com.exercise.AndroidTwoView;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class View2 extends View {

private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
float cx = 0, cy =0;
boolean draw = false;

public View2(Context context) {
super(context);
init();
}

public View2(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public View2(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}

private void init(){
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
paint.setColor(Color.BLUE);
setFocusable(true);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}

@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
if (draw){
canvas.drawCircle(cx, cy, 3, paint);
}
}

@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
int action = event.getAction();

if (action==MotionEvent.ACTION_DOWN){
cx = event.getX();
cy = event.getY();
draw = true;
invalidate();
}

return true;
}

}


- Modify main.xml
<?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.exercise.AndroidTwoView.View1
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
<com.exercise.AndroidTwoView.View2
android:layout_width="fill_parent"
android:layout_height="fill_parent"
/>
</FrameLayout>


- AndroidTwoView.java actually no change from the auto-generated.

Download the files.

Android SurfaceView, run in Thread with sleep()

Further works on the last exercise "Android SurfaceView", a call to sleep(500) is introduced in run() of the thread, such that the onDraw() will be called in half second.

In this exercise, there is a point on the screen. When user touch on screen, the point will run from the current position to the touched position, in speed of half the distance per second.

Android SurfaceView, run in Thread with sleep()

AndroidSurfaceViewUI.java
package com.exercise.AndroidSurfaceViewUI;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class AndroidSurfaceViewUI extends Activity {

private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

private float initX, initY;
private float targetX, targetY;
private boolean drawing = true;

public class MySurfaceThread extends Thread {

private SurfaceHolder myThreadSurfaceHolder;
private MySurfaceView myThreadSurfaceView;
private boolean myThreadRun = false;

public MySurfaceThread(SurfaceHolder surfaceHolder, MySurfaceView surfaceView) {
myThreadSurfaceHolder = surfaceHolder;
myThreadSurfaceView = surfaceView;
}

public void setRunning(boolean b) {
myThreadRun = b;
}

@Override
public void run() {
// TODO Auto-generated method stub
//super.run();
while (myThreadRun) {
Canvas c = null;
try {
c = myThreadSurfaceHolder.lockCanvas(null);
synchronized (myThreadSurfaceHolder) {
myThreadSurfaceView.onDraw(c);
}

sleep(500);

} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} 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) {
myThreadSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback{

private MySurfaceThread thread;

@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
//super.onDraw(canvas);
if(drawing){
canvas.drawRGB(0, 0, 0);
canvas.drawCircle(initX, initY, 3, paint);
if ((initX==targetX) && (initY==targetY)){
drawing = false;
}
else{
initX = (initX + targetX)/2;
initY = (initY + targetY)/2;
}
}
}

@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
//return super.onTouchEvent(event);
int action = event.getAction();

if (action==MotionEvent.ACTION_DOWN){
targetX = event.getX();
targetY = event.getY();
drawing = true;
}

return true;
}

public MySurfaceView(Context context) {
super(context);
// TODO Auto-generated constructor stub
init();
}

public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
init();
}

public MySurfaceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
init();
}

private void init(){
getHolder().addCallback(this);
thread = new MySurfaceThread(getHolder(), this);

setFocusable(true); // make sure we get key events

paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
paint.setColor(Color.WHITE);

initX = targetX = 0;
initY = targetY = 0;

}

@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,
int arg3) {
// TODO Auto-generated method stub
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
thread.setRunning(true);
thread.start();
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
}

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);

MySurfaceView mySurfaceView = new MySurfaceView(this);
setContentView(mySurfaceView);
}
}



Download the file.

Monday 24 May 2010

Google Needs to be More Focused

Google LogoJohn C Dvorak thinks Google needs to be more focused. I agree. Right now they are making a lot of money, but they seem to want to dabble in everything. What sets Google apart?  Internet Search?  Contextual advertising?  Cloud Computing?



Maybe Google just makes the Net easier to use?  Much like Apple has made the PC and the phone easier to use. Google has been a very positive force on the Net so I hope they figure it out.

Sunday 23 May 2010

Android SurfaceView

It's a SurfaceView variation from the last exercise "Custom View with User Interaction".

Android SurfaceView

- Implement a new class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback.

- Implement the surfaceCreated(), surfaceDestroyed(), surfaceChanged() methods for SurfaceHolder.Callback.

- Implement a Thread for our SurfaceView.

- In SurfaceView's constructor, add the SurfaceView to SurfaceHolder for a callback, and create the Thread.
getHolder().addCallback(this);
thread = new MySurfaceThread(getHolder(), this);

- Modify the run() method of the Thread

package com.exercise.AndroidSurfaceViewUI;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class AndroidSurfaceViewUI extends Activity {

private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

private float initX, initY, radius;
private boolean drawing = false;

public class MySurfaceThread extends Thread {

private SurfaceHolder myThreadSurfaceHolder;
private MySurfaceView myThreadSurfaceView;
private boolean myThreadRun = false;

public MySurfaceThread(SurfaceHolder surfaceHolder, MySurfaceView surfaceView) {
myThreadSurfaceHolder = surfaceHolder;
myThreadSurfaceView = surfaceView;
}

public void setRunning(boolean b) {
myThreadRun = b;
}

@Override
public void run() {
// TODO Auto-generated method stub
//super.run();
while (myThreadRun) {
Canvas c = null;
try {
c = myThreadSurfaceHolder.lockCanvas(null);
synchronized (myThreadSurfaceHolder) {
myThreadSurfaceView.onDraw(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) {
myThreadSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
}
}

public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback{

private MySurfaceThread thread;

@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
//super.onDraw(canvas);
if(drawing){
canvas.drawCircle(initX, initY, radius, paint);
}
}

@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub
//return super.onTouchEvent(event);
int action = event.getAction();
if (action==MotionEvent.ACTION_MOVE){
float x = event.getX();
float y = event.getY();
radius = (float) Math.sqrt(Math.pow(x-initX, 2) + Math.pow(y-initY, 2));
}
else if (action==MotionEvent.ACTION_DOWN){
initX = event.getX();
initY = event.getY();
radius = 1;
drawing = true;
}
else if (action==MotionEvent.ACTION_UP){
drawing = false;
}

return true;
}

public MySurfaceView(Context context) {
super(context);
// TODO Auto-generated constructor stub
init();
}

public MySurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
init();
}

public MySurfaceView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
init();
}

private void init(){
getHolder().addCallback(this);
thread = new MySurfaceThread(getHolder(), this);

setFocusable(true); // make sure we get key events

paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
paint.setColor(Color.WHITE);
}

@Override
public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,
int arg3) {
// TODO Auto-generated method stub
}

@Override
public void surfaceCreated(SurfaceHolder holder) {
// TODO Auto-generated method stub
thread.setRunning(true);
thread.start();
}

@Override
public void surfaceDestroyed(SurfaceHolder holder) {
// TODO Auto-generated method stub
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
}

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);

MySurfaceView mySurfaceView = new MySurfaceView(this);
setContentView(mySurfaceView);
}
}


(In the View version in "Custom View with User Interaction", the view is forced to re-draw by invalidate() method, so the screen will be cleared. In this SurfaceView version, the onDraw() method is called inside thread's Run() method, so the drawing on the original canvas will not be cleared.)

Download the file.


next: Android SurfaceView, run in Thread with sleep()







It's a bug here!


Pls. refer to the article
"IllegalThreadStateException in LunarLander"
for details.


I'm Sorry about that! ()



Saturday 22 May 2010

Custom View with User Interaction

In my previous exercise "Draw a bitmap on View", "Draw something on a Canvas" and even "Android FaceDetector", the application output is drawn on View in onDraw method in application starting once only; without any interaction with user. In this exercise, a custom View with user interaction will be implemented. When user touch on the screen and move, a circle will be drawn.

Custom View with User Interaction

There is a customized MyView class, extends from View. The onTouchEvent(MotionEvent event) method is used to handle the user touch events; such as ACTION_MOVE, ACTION_DOWN and ACTION_UP. Then it calls invalidate() to force the view to be redraw, onDraw(Canvas canvas) will be called in turn.

AndroidViewUI.java
package com.exercise.AndroidViewUI;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class AndroidViewUI extends Activity {
public class MyView extends View {

private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);

private float initX, initY, radius;
private boolean drawing = false;

public MyView(Context context) {
super(context);
// TODO Auto-generated constructor stub
init();
}

public MyView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
init();
}

public MyView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
init();
}

private void init(){
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(3);
paint.setColor(Color.WHITE);
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
MeasureSpec.getSize(heightMeasureSpec));
}

@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
if(drawing){
canvas.drawCircle(initX, initY, radius, paint);
}
}

@Override
public boolean onTouchEvent(MotionEvent event) {
// TODO Auto-generated method stub

int action = event.getAction();
if (action==MotionEvent.ACTION_MOVE){
float x = event.getX();
float y = event.getY();

radius = (float) Math.sqrt(Math.pow(x-initX, 2) + Math.pow(y-initY, 2));

}
else if (action==MotionEvent.ACTION_DOWN){
initX = event.getX();
initY = event.getY();
radius = 1;
drawing = true;
}
else if (action==MotionEvent.ACTION_UP){
drawing = false;
}
invalidate();
return true;
}

}



/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);

MyView myView = new MyView(this);
setContentView(myView);
}


}


Download the file.

Related Article: Instance two object from the same custom view class



Thursday 20 May 2010

Android 2.2 platform available for the Android SDK

The Android 2.2 platform is now available for the Android SDK, along with new tools, documentation, and a new NDK.

Android 2.2 is a minor platform release including user features, developer features, API changes, and bug fixes. For information on developer features and API changes, see the Framework API section.

Android 2.2
Android 2.2

For developers, the Android 2.2 platform is available as a downloadable component for the Android SDK. The downloadable platform includes a fully compliant Android library and system image, as well as a set of emulator skins, sample applications, and more. The downloadable platform includes no external libraries.

You can refer to my article to know how to Install Android SDK on Eclipse 3.5 Galileo, in Ubuntu 9.10. Or, if you have install Android 1.6 SDK or later version, you can refer to another article to upgrade Android SDK using Android SDK and AVD Manager.





Custom JQuery Form Validation Plugin

JQuery LogoCheck out this custom form validation plugin by AJ Graham. Worth a look.

Network Tethering for the iPhone?

Looks like we may finally get tethering for the iPhone. Probably not for free though, expect extra charges for bandwidth.

Wednesday 19 May 2010

TinyUmbrella - Unified TinyTSS and The Firmware Umbrella in ONE!

03.13.83 - Changes



  • Fixed a bug for ppc users. Sorry minimum osx required version is 10.5 - I couldn't get the thing working in 10.4 because of some dependency changes from 10.4 -> 10.5.

  • Should now work on mac mini's if you have Leopard.

  • Cosmetic changes

  • Some small refactorings to get ready for ios4.







03.13.81 - Changes

  • OK so I fixed a huge error that rendered restores basically impossible because of some stupid oversight. Please update to 03.13.81 so you can correctly restore your device and not fail at the iTunes screen :) Thanks guys - keep the bug reports coming!

  • I can confirm that restores work as I've used my own iPhone 3GS on 3.1.2 as a test.

03.13.79 - Changes

  • Detects if port 80 is in use and should properly report why it cannot start. I may add a feature to detect the actual PID that is using port 80.

  • Added the region info to the model number to aid users in finding the carrier to which their device is locked. IE: Model Numbers in the USA that end in LL/A are locked to AT&T.

  • Various and sundry minor defect/exception fixes.

03.13.73 - Changes

  • Added Log instead of a single label for feedback

  • Added context menu for ECID text and the Log console


    • Copy - Copy the whole ecid to clipboard

    • Copy (Hex) - Copy the ecid as Hex number

    • Paste - Paste the ECID in the clipboard

    • Clear - Duh?


  • Pastebin submission of log file (right click -> Pastebin Log)

  • Hovering over ECID field will display a tooltip of the ECID in Hex format

  • The OSX version no longer needs to be started twice the very first time its launched! (I no longer fail at execl!)

  • The OSX version now should work on Leopard (10.5)

  • The OSX version can be dragged into the /Applications folder





TinyUmbrella is ready for testing. I've done most of what I said I would do in the last post. TinyUmbrella is a combination SHSH file saver as well as local TSS server. For those of you that have no idea what that is I'll explain.



If you have one of the following devices:

  • iPhone 3GS

  • iPod Touch 3rd Generation (32 or 64)

  • iPod Touch 2nd Generation (with a model number MC) 

Then you have probably noticed that Apple does not let you go back down to older firmware versions for your device. Once you 'upgrade' they don't let you downgrade. The way they stop you is basically by a simple response that iTunes receives when you try to restore the firmware of your choosing. See, the firmware is now signed for the above devices. It is signed with a unique id (ECID) that only your device has. Apple takes the firmware version files and combines them with your ECID and generates a hash that ONLY APPLE can generate. iTunes packages up this valuable information and sends it to your device. Your device checks the information and verifies the signature (making sure it really came from Apple because it CANNOT be forged as the encryption is very high). If the signature matches then the restore process can continue. If the signature does not match, the device raises an error and the restore process stops. Thanks to Saurik (Jay Freeman) we all now know how to circumvent this. Apple only provides this signature for firmware versions while the firmware version is 'Active'. So once a new firmware version is out, Apple stops signing the older version and only signs the new version. This is why you cannot get 3.1.2 signatures anymore. Apple is currently only signing 3.1.3 signatures (and 3.2 for iPads). This will change when the next firmware is released. Soon 3.1.3 (and 3.2) firmware signatures will be a thing of the past and Apple will only sign the next one. The version is important. If you do not have the EXACT signatures for the exact device (ECID) for the exact firmware version, you cannot restore that device to that version. PERIOD. So, if there was a way to save that signature for later use, we could bypass the need for asking Apple (only to have them say 'No') to restore our firmware. If you have a jailbroken device, you can visit cydia and your shsh will be saved 'on-file'. But if you are not jailbroken yet, or if a jailbreak has not yet been released for the firmware your device is on, you are just out of luck as you can only get cydia if you are jailbroken. This is where TinyUmbrella comes in.





TinyUmbrella sends the same exact request that iTunes sends Apple when requesting the signatures for your device to be restored. The difference is that TinyUmbrella does not need to do anything to your device. You do not need to be on the firmware version that TinyUmbrella is requesting signatures. As an example, I've been on 3.1.2 for quite some time. TinyUmbrella has saved my 3.1, 3.1.2 and 3.1.3 signatures. This is because the request that iTunes sends to Apple includes your ECID and the details about the firmware version. Apple sends the response signatures and TinyUmbrella saves them so you can use them whenever you want; Even if Apple has stopped signing that particular version.





The latest version of TinyUmbrella contains a small server that forces iTunes to connect to it instead of going to the apple servers. When you run TinyUmbrella and start the TSS server, your saved SHSH signatures will be cached up and ready for use. Once TinyUmbrella is running and the TSS server is running. You can open iTunes and start your restore to the firmware version you have SHSH files for. iTunes will accept the responses as if they had come from Apple itself.





It is important to realize that Apple can disable this in a future iTunes update. Currently iTunes v9.1.1 (12) works perfectly. Always be wary of any iTunes updates. Feel free to follow me on twitter (@notcom) and I'll let you know if iTunes versions are TinyUmbrella - safe. 



TinyUmbrella is also able to detect your ECID so you don't have to do anything like enter recovery and search through obscure system windows to find your ECID. It's automatic. All you have to do to save your SHSH is press the Save My SHSH button. TinyUmbrella even makes the request through Cydia so that Cydia will have your device's SHSH signatures 'on-file' immediately. This gives you double protection of having your SHSH signatures locally as well as on Saurik's trusted servers.





I put a lot of work on this little tool. I hope it helps many of you restore your devices even after Apple thinks you shouldn't be able to. In my opinion, I should be the final say when it comes to what versions of what software runs on my device. I think you feel the same way too.





I've made the OSX and Windows versions of TinyUmbrella available for beta testing. It's an open beta so feel free to give it a try. Let me know if you come across any issues. Here are the system requirements:





OSX

  • OSX 10.6.x - known issues on 10.5

  • Java 1.5+ (ships with 10.6)

  • iTunes 9.0 - iTunes 9.1.1 (25) (other versions I'm not 100%)

  • Administrator Privileges to run on port 80 and make /etc/hosts modifications

Windows

  • Windows XP, Vista, 7 (x86 or 64bit)

  • Java 32 bit (VERY important - will not work if you have 64 bit Java running)

  • iTunes 9.0 - iTunes 9.1.1 (25) (I've personally tested on 9.0.0 and 9.1.1)

  • Administrator privileges for running on port 80 and making hosts changes

TinyUmbrella makes the necessary configuration changes for you on-the-fly so that you don't have to play with your hosts file in order to route iTunes to your computer. I also save your shsh files in your home directory under the directory called ".shsh". You can also drag and drop old shsh files you have created from previous versions of umbrella onto the TinyUmbrella window and they will be renamed and saved to the .shsh directory for use by the TSS server.





I plan on fixing bugs before release and getting the Linux version completely working before calling it a release.

Android FaceDetector

Android provide a class android.media.FaceDetector to identify the faces of people in a Bitmap graphic object.

Android FaceDetector

It's a simple exercise of face detection on Android. You have to place your own photo (in 320x480) into /res/drawable folder, or use the attached photos from the download link on the bottom of the text.

package com.exercise.AndroidFaceDetector;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PointF;
import android.media.FaceDetector;
import android.media.FaceDetector.Face;
import android.os.Bundle;
import android.view.View;

public class AndroidFaceDetector extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
setContentView(new myView(this));
}

private class myView extends View{

private int imageWidth, imageHeight;
private int numberOfFace = 5;
private FaceDetector myFaceDetect;
private FaceDetector.Face[] myFace;
float myEyesDistance;
int numberOfFaceDetected;

Bitmap myBitmap;

public myView(Context context) {
super(context);
// TODO Auto-generated constructor stub

BitmapFactory.Options BitmapFactoryOptionsbfo = new BitmapFactory.Options();
BitmapFactoryOptionsbfo.inPreferredConfig = Bitmap.Config.RGB_565;
myBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.face5, BitmapFactoryOptionsbfo);
imageWidth = myBitmap.getWidth();
imageHeight = myBitmap.getHeight();
myFace = new FaceDetector.Face[numberOfFace];
myFaceDetect = new FaceDetector(imageWidth, imageHeight, numberOfFace);
numberOfFaceDetected = myFaceDetect.findFaces(myBitmap, myFace);

}

@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub

canvas.drawBitmap(myBitmap, 0, 0, null);

Paint myPaint = new Paint();
myPaint.setColor(Color.GREEN);
myPaint.setStyle(Paint.Style.STROKE);
myPaint.setStrokeWidth(3);

for(int i=0; i < numberOfFaceDetected; i++)
{
Face face = myFace[i];
PointF myMidPoint = new PointF();
face.getMidPoint(myMidPoint);
myEyesDistance = face.eyesDistance();
canvas.drawRect(
(int)(myMidPoint.x - myEyesDistance),
(int)(myMidPoint.y - myEyesDistance),
(int)(myMidPoint.x + myEyesDistance),
(int)(myMidPoint.y + myEyesDistance),
myPaint);
}
}
}
}



Download the files.

Related:
- Face detection for Camera



iPad: GoodReader is really good for PDFs

Hey iPad fans out there. I have been looking for a good PDF reader and I have finally found one. Get GoodReader for $1. It reads not only PDFs, but .txt files, Word files, and a number of other formats.



For the iPad, its the best thing out there. It actually creates a Table of Contents for most PDF documents. Which is a requirement for most large documents and is a feature left out of most PDF readers for the iPad. You can size documents with gestures which is nice. My only complaint is you navigate through documents my clicking at the top and bottom of a page which is counter to the prevailing standard of clicking left on right for most other readers on the iPad. When I have time I can hopefully provide a better review. But for now, if you need to read PDFs, get this app!!!!

JQuery Circular Animation

JQuery LogoWant to add a cool animation effect to your site? Check out this circular animation implemented as a JQuery plugin. Pretty cool.

Sunday 16 May 2010

TinyUmbrella - Coming Soon.

Edit: Thank you @iOPK for the awesome header ;)



Umbrella and TinyTSS are in the process of being merged into a single app. Basically I've added a button to the new Umbrella UI. This button allows you to turn on and off the TinyTSS server. I'm also adding a ton of features to the app as a whole to make it more user friendly. Here are some ideas I'm currently working on / have already implemented locally (on my machine).

  • Auto configuration of the hosts file - no more messing with the hosts - I do it for you (with the backup being named hosts.umbrella) yes for Windows, Mac, and Linux (If you can figure out a way to restore using Linux that is :) ).

  • Umbrella now will save your shsh files silently in a new directory (~/.shsh). This allows the built in TSS server to read all of the files in that directory and cache them.

  • Umbrella will support drag and drop. My goal will be for you to be able to drag your .shsh files and drop them onto Umbrella and Umbrella will save the files to the ~/.shsh directory. I also plan on supporting the OLD 00.shsh 01.shsh 02.shsh files as well :)

  • [OSX ONLY] - Because Umbrella's built in TSS will need to run on port 80, Umbrella will ask you for your superuser password just once (Through the Authorization mechanism - which means your operating system will ask for your permission not me). This will explain the admin popup you will receive when Umbrella runs for the first time on your machine. After this first time, the application will exit and the next time you run Umbrella it will be able to start the service on port 80.

  • Already mentioned but this is still a HUGE code change - Umbrella's built in TSS server will cache multiple .shsh files (dragging and dropping shsh files onto Umbrella will add them to the TSS cache immediately). This also means that the TSS server will now intelligently respond to the request based on the ECID in the request instead of stupidly spitting out whatever data is in the .shsh file loaded at startup.

I'm in the process of integrating the two applications now. The TinyTSS start button will be next to the 'Save My SHSH' button and will simply say 'Start' when the server is down and 'Stop' when the server is up. A label below the button will indicate the servers status. I'm thinking of putting a small ? icon for the user to click on to be able to see the .shshs loaded as a list of devices. ie.

  • iPhone3GS 3.1.2 (7D11) - 123412341234

  • iPhone3GS 3.1.3 (7E18) - 123412341234

  • iPod3G 3.1.3 (7E18) - 222233334444

  • iPod2G 3.1.2 (7D11) - 111133332222

This will give the user a better indication of what devices can be restored on this machine running Umbrella with the built in TSS server.





I plan on getting this stuff wrapped up over the next week or so. Trust me - it's going to make life so much easier for everyone that has no idea what java -jar tinytss.jar means.





:)

Friday 14 May 2010

WebTip: Gray Shade Suddenly Covers Half Your Site

Problem: You load your site in Firefox. The upper half of the page is covered by a gray shade and you can't click on anything under it. This problem seems to have randomly appeared.



Solution: This appears to be a bug with Google Analytics and Firefox (3.6.3 for me). If you use the Analytics overlay feature to view a site, then the next time you try to view the site in Firefox, you have this problem. To fix it, simply delete all your cookies. Or if you can find it, only delete the analytics cookies.



Note: If you look at your code in Firebug, you will see Analytics has added this code to your page.



<div id="ga_shade" style="background-color: #eeeeee; display: block; height: 1000px; left: 0px; opacity: 0.5; position: absolute; top: 0px; width: 100%; z-index: 100000;"></div>



Removing the cookie removes the above HTML code from your page.

Draw something on a Canvas

Simple draw something (drawRect) on a Canvas over a bitmap on a View.



Modify from the previous article "Draw a bitmap on View". In order to draw something, a object of Paint class have to been create, and set some parameter; such as color, style, Strok.

Modify the code:
package com.exercise.AndroidPaint;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.view.View;

public class AndroidPaint extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
setContentView(new myView(this));
}

private class myView extends View{

public myView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}

@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
Bitmap myBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.googlelogo320x480);
canvas.drawBitmap(myBitmap, 0, 0, null);

Paint myPaint = new Paint();
myPaint.setColor(Color.GREEN);
myPaint.setStyle(Paint.Style.STROKE);
myPaint.setStrokeWidth(3);
canvas.drawRect(10, 10, 100, 100, myPaint);
}
}
}


Download the files.

Next: Android FaceDetector
Next: Custom View with User Interaction



Thursday 13 May 2010

So Much for the NetBook Experiment



Well I have been playing around with a 10" HP Netbook for the last 6 months or so. I have taken it on business trips, vacations, and just around town. My goal was  to use it as much as possible and see how useful it is.



My conclusion.  Netbooks are just too damn small. lol. From a purely technical point of view, there is nothing to complain about. The Intel Atom CPU has plenty of power and Windows 7 is a pretty sweet OS. The HP has a great screen and really good battery life. Surfing the Web on Chrome or Firefox is good. And you can hardly feel it when you carry it around.



But have you ever tried to put the thing in your lap and work on it for more than 15 minutes?  Or actually do some writing or coding on the screen?  It just doesn't fit or work well. I feel like I have stolen a device from a gnome. lol.



Thus my enthusiasm for the iPad in a previous post. Instead of feeling small, it feels just about right. So its back to my 13" Macbook for actually getting something done and being portable.

Umbrella Released with device detection on OSX, Windows, Linux

Edit: I've re-added the v222 zip for TinyTSS while I work on the rewrite.



I've finished the first round of fixes with Umbrella. Umbrella now supports mac, windows, and linux although linux is a bit flaky until I iron out some (non critical) defects. (I'm aware of the segmentation fault but I haven't pinned it down)



Eventually Umbrella and TinyTSS will be combined into the same application. Now that I've gotten device detection finished on Umbrella I can work on the TinyTSS rewrite.



As always keep me posted if you find any bugs.



Special thanks to @iOPK for the icons!



umbrella.exe md5: 3c762b9b2087b97d17599079d2527667

Umbrella-03.13.32.dmg md5: 2849f10bb0056bb9c1dca66e2d688f06

umbrella-linux-03.13.32.zip: ba176cfd27464f2d38d59378145b82c1

Wednesday 12 May 2010

Draw a bitmap on View

It's a simple example to draw a bitmap on screen in View. The content view is set to a View, and the drawing is achieved in onDraw method.

Draw a bitmap on View

To make it simple, a bitmap of 320x480 (the size of HVGA) was prepared. It's in /res/drawable/ folder, and will be loaded using the following codes:

Bitmap myBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.googlelogo320x480);

canvas.drawBitmap(myBitmap, 0, 0, null);

package com.exercise.AndroidPaint;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.os.Bundle;
import android.view.View;

public class AndroidPaint extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.main);
setContentView(new myView(this));
}

private class myView extends View{

public myView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}

@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
Bitmap myBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.googlelogo320x480);
canvas.drawBitmap(myBitmap, 0, 0, null);
}
}
}


Download the files.

Next: Draw something on a Canvas.

Tuesday 11 May 2010

Switching to a New Feed

Blogger has a new feed system so I'm switching over to it.



The new feed URL is:

http://feeds.feedburner.com/blogspot/wBnvR



I am guessing the old feed URL will continue to work for a while. Please update your reader. Thanks.

Monday 10 May 2010

File Manager in iPod/iPad OS?

Check out this post on a file manager being included in the iPod 4 operating system.



This is exciting news, as the iPod, iPhone, and iPad could really use a global file/folder space. Right now, it seems like each application is sort of self contained. It would be nice to be able to share HTML files and the like. It will be interesting to see how this work and how applications will interact with it. If its included in the final version. :)

A simple RSS reader III, show details once item clicked.

Last article "A simple RSS reader II, implement with RSSFeed & RSSItem", only the titles shown in a List. Here, we are going to start another activity to show the details once any item in the list clicked.

A simple RSS reader III, show details once item clicked.

Most of the works from the last article "A simple RSS reader II, implement with RSSFeed & RSSItem", with the changes lsited below.

Implement /res/layout/details.xml, it's the layout of the activity ShowDetails.java
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="RSS Details:-" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/detailstitle" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/detailsdescription" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:id="@+id/detailslink" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/detailspubdate" />
</LinearLayout>


Implement a new Activity /src/com.exercise.AndroidRssReader/ShowDetails.java, it will be started once any item in the list clicked.
package com.exercise.AndroidRssReader;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class ShowDetails extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.details);
TextView detailsTitle = (TextView)findViewById(R.id.detailstitle);
TextView detailsDescription = (TextView)findViewById(R.id.detailsdescription);
TextView detailsPubdate = (TextView)findViewById(R.id.detailspubdate);
TextView detailsLink = (TextView)findViewById(R.id.detailslink);

Bundle bundle = this.getIntent().getExtras();

detailsTitle.setText(bundle.getString("keyTitle"));
detailsDescription.setText(bundle.getString("keyDescription"));
detailsPubdate.setText(bundle.getString("keyPubdate"));
detailsLink.setText(bundle.getString("keyLink"));

}
}


Modify /src/com.exercise.AndroidRssReader/AndroidRssReader.java to start new activity once any list item clicked.
package com.exercise.AndroidRssReader;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.app.ListActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;

public class AndroidRssReader extends ListActivity {

private RSSFeed myRssFeed = null;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

try {
URL rssUrl = new URL("http://www.gov.hk/en/about/rss/govhkrss.data.xml");
SAXParserFactory mySAXParserFactory = SAXParserFactory.newInstance();
SAXParser mySAXParser = mySAXParserFactory.newSAXParser();
XMLReader myXMLReader = mySAXParser.getXMLReader();
RSSHandler myRSSHandler = new RSSHandler();
myXMLReader.setContentHandler(myRSSHandler);
InputSource myInputSource = new InputSource(rssUrl.openStream());
myXMLReader.parse(myInputSource);

myRssFeed = myRSSHandler.getFeed();

} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

if (myRssFeed!=null)
{
TextView feedTitle = (TextView)findViewById(R.id.feedtitle);
TextView feedDescribtion = (TextView)findViewById(R.id.feeddescribtion);
TextView feedPubdate = (TextView)findViewById(R.id.feedpubdate);
TextView feedLink = (TextView)findViewById(R.id.feedlink);
feedTitle.setText(myRssFeed.getTitle());
feedDescribtion.setText(myRssFeed.getDescription());
feedPubdate.setText(myRssFeed.getPubdate());
feedLink.setText(myRssFeed.getLink());

ArrayAdapter<RSSItem> adapter =
new ArrayAdapter<RSSItem>(this,
android.R.layout.simple_list_item_1,myRssFeed.getList());
setListAdapter(adapter);

}
}

@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// TODO Auto-generated method stub
Intent intent = new Intent(this,ShowDetails.class);
Bundle bundle = new Bundle();
bundle.putString("keyTitle", myRssFeed.getItem(position).getTitle());
bundle.putString("keyDescription", myRssFeed.getItem(position).getDescription());
bundle.putString("keyLink", myRssFeed.getItem(position).getLink());
bundle.putString("keyPubdate", myRssFeed.getItem(position).getPubdate());
intent.putExtras(bundle);
startActivity(intent);
}
}


Modify AndroidManifest.xml to include the new activity.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.exercise.AndroidRssReader"
android:versionCode="1"
android:versionName="1.0">
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".AndroidRssReader"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".ShowDetails"></activity>
</application>
<uses-sdk android:minSdkVersion="4" />
<uses-permission android:name="android.permission.INTERNET" />
</manifest>


Download the files.

next:
- Apply custom adapter to ListView for RSS Reader
- A simple RSS reader IV, start browser to open the selected feed



Saturday 8 May 2010

A simple RSS reader II, implement with RSSFeed & RSSItem

In the previous article "A simple RSS reader, in ListView", I just show a very simple RSS reader, with a very limited handling on the RSSHandle events. Here, I will show in more details, also in better orgination.

A simple RSS reader II, implement with RSSFeed & RSSItem

First of all, the source of the feed is changed to http://www.gov.hk/en/about/rss/govhkrss.data.xml, it's a feed from Hong Kong Government; because the original blogger's feed is too complicated for me to demo here!

Here, RSSHandler, RSSFeed and RSSItems are implemented as separated class for better organization. The application have a single object of RSSFeed class, which consist a list of RSSItem object. More details (Title, PubDate, Description and Link) of the feed and individual items are saved in the structure, which can be retrieved later.

AndroidManifest.xml to grant "android.permission.INTERNET" to the application. (Refer to last article "A simple RSS reader, using Android's org.xml.sax package")

Keep the rsslist.xml as before
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rowtext"
android:layout_width="fill_parent"
android:layout_height="25px"
android:textSize="10sp" />


Modify main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
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" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/feedtitle" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/feeddescribtion" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:id="@+id/feedpubdate" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:autoLink="web"
android:id="@+id/feedlink" />
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@android:id/empty"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="No Data" />
</LinearLayout>


Implement /src/com.exercise.AndroidRssReader/RSSItem.java
package com.exercise.AndroidRssReader;

public class RSSItem {

private String title = null;
private String description = null;
private String link = null;
private String pubdate = null;

RSSItem(){
}

void setTitle(String value)
{
title = value;
}
void setDescription(String value)
{
description = value;
}
void setLink(String value)
{
link = value;
}
void setPubdate(String value)
{
pubdate = value;
}

String getTitle()
{
return title;
}
String getDescription()
{
return description;
}
String getLink()
{
return link;
}
String getPubdate()
{
return pubdate;
}

@Override
public String toString() {
// TODO Auto-generated method stub
return title;
}
}


Implement /src/com.exercise.AndroidRssReader/RSSFeed.java
package com.exercise.AndroidRssReader;

import java.util.List;
import java.util.Vector;

public class RSSFeed {
private String title = null;
private String description = null;
private String link = null;
private String pubdate = null;
private List<RSSItem> itemList;

RSSFeed(){
itemList = new Vector<RSSItem>(0);
}

void addItem(RSSItem item){
itemList.add(item);
}

RSSItem getItem(int location){
return itemList.get(location);
}

List<RSSItem> getList(){
return itemList;
}

void setTitle(String value)
{
title = value;
}
void setDescription(String value)
{
description = value;
}
void setLink(String value)
{
link = value;
}
void setPubdate(String value)
{
pubdate = value;
}

String getTitle()
{
return title;
}
String getDescription()
{
return description;
}
String getLink()
{
return link;
}
String getPubdate()
{
return pubdate;
}

}


Implemenet /src/com.exercise.AndroidRssReader/RSSHandler.java
package com.exercise.AndroidRssReader;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

public class RSSHandler extends DefaultHandler {

final int state_unknown = 0;
final int state_title = 1;
final int state_description = 2;
final int state_link = 3;
final int state_pubdate = 4;
int currentState = state_unknown;

RSSFeed feed;
RSSItem item;

boolean itemFound = false;

RSSHandler(){
}

RSSFeed getFeed(){
return feed;
}

@Override
public void startDocument() throws SAXException {
// TODO Auto-generated method stub
feed = new RSSFeed();
item = new RSSItem();

}

@Override
public void endDocument() throws SAXException {
// TODO Auto-generated method stub
}

@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
// TODO Auto-generated method stub

if (localName.equalsIgnoreCase("item")){
itemFound = true;
item = new RSSItem();
currentState = state_unknown;
}
else if (localName.equalsIgnoreCase("title")){
currentState = state_title;
}
else if (localName.equalsIgnoreCase("description")){
currentState = state_description;
}
else if (localName.equalsIgnoreCase("link")){
currentState = state_link;
}
else if (localName.equalsIgnoreCase("pubdate")){
currentState = state_pubdate;
}
else{
currentState = state_unknown;
}

}

@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
// TODO Auto-generated method stub
if (localName.equalsIgnoreCase("item")){
feed.addItem(item);
}
}

@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
// TODO Auto-generated method stub

String strCharacters = new String(ch,start,length);

if (itemFound==true){
// "item" tag found, it's item's parameter
switch(currentState){
case state_title:
item.setTitle(strCharacters);
break;
case state_description:
item.setDescription(strCharacters);
break;
case state_link:
item.setLink(strCharacters);
break;
case state_pubdate:
item.setPubdate(strCharacters);
break;
default:
break;
}
}
else{
// not "item" tag found, it's feed's parameter
switch(currentState){
case state_title:
feed.setTitle(strCharacters);
break;
case state_description:
feed.setDescription(strCharacters);
break;
case state_link:
feed.setLink(strCharacters);
break;
case state_pubdate:
feed.setPubdate(strCharacters);
break;
default:
break;
}
}

currentState = state_unknown;
}


}


Modify /src/com.exercise.AndroidRssReader/AndroidRssReader.java
package com.exercise.AndroidRssReader;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;

import android.app.ListActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class AndroidRssReader extends ListActivity {

private RSSFeed myRssFeed = null;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

try {
URL rssUrl = new URL("http://www.gov.hk/en/about/rss/govhkrss.data.xml");
SAXParserFactory mySAXParserFactory = SAXParserFactory.newInstance();
SAXParser mySAXParser = mySAXParserFactory.newSAXParser();
XMLReader myXMLReader = mySAXParser.getXMLReader();
RSSHandler myRSSHandler = new RSSHandler();
myXMLReader.setContentHandler(myRSSHandler);
InputSource myInputSource = new InputSource(rssUrl.openStream());
myXMLReader.parse(myInputSource);

myRssFeed = myRSSHandler.getFeed();

} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ParserConfigurationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SAXException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}

if (myRssFeed!=null)
{
TextView feedTitle = (TextView)findViewById(R.id.feedtitle);
TextView feedDescribtion = (TextView)findViewById(R.id.feeddescribtion);
TextView feedPubdate = (TextView)findViewById(R.id.feedpubdate);
TextView feedLink = (TextView)findViewById(R.id.feedlink);
feedTitle.setText(myRssFeed.getTitle());
feedDescribtion.setText(myRssFeed.getDescription());
feedPubdate.setText(myRssFeed.getPubdate());
feedLink.setText(myRssFeed.getLink());

ArrayAdapter<RSSItem> adapter =
new ArrayAdapter<RSSItem>(this,
android.R.layout.simple_list_item_1,myRssFeed.getList());
setListAdapter(adapter);
}
}
}


Download the files.

Next: A simple RSS reader III, show details once item clicked.