Facebook BlackBerry SDK‎ > ‎Archive‎ > ‎

Sample Codes

Java Facebook API for BlackBerry

Author: Eki Y. Baskoro (eki at baskoro dot web dot id)
Last updated: 03 April 2010
Changes
13/11/2009 14:22 GMT +7 Created document.
28/12/2009 21:25 GMT +7 Cleaned up TestBB class.
Added pictureUrl and pictureSmallUrl properties to User class.
Updated FacebookFacade class to retrieve pictureUrl and pictureSmallUrl properties for User.
Updated FacebookFacade class to retrieve stream data for given logged in User.
03/04/2010 01:42 GMT +7 Changed title from Facebook Connect on Blackberry.
Added documentations.
Reworked on the LoginScreen to change the URL so that extended permissions can be granted correctly.
Updated FacebookFacade class to enable stream publishing.
Updated FacebookFacade class to have hasAppPermission method.
Added ActionLink class for stream publishing.
Added Attachment class for stream publishing.
Added Privacy and CustomPrivacy classes for stream publishing privacy settings.
Added Media, FlashMedia, ImageMedia and Mp3Media classes for stream publishing media attachments.
Added Post and Stream classes for stream publishing.
Added FacebookException class.

The following is a short HOWTO on using Facebook Connect on Blackberry. I created a simple Facade encapsulating the Facebook REST API as well as added 'rough' MVC approach for screen navigation. I have tested on JDE 4.5 using 8320 simulator. This is still work in progress and all codes have MIT license. Please provide your feedback by contacting me via email.

Prerequisites

You will need to download JSON ME from this link and extract it to your source directory.

Codes

ActionListener is a listener that processes action result from an AbstractScreen.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.action;

import blackberry.ui.AbstractScreen;

/**
 * ActionListener
 * 
 * Listens for action result.
 * 
 * @author Eki Baskoro
 *
 */
public interface ActionListener {

	/**
	 * Process action result
	 * 
	 * @param action is the action result
	 * @param screen is the screen where the action is generated from
	 */
	public void execute(String action, AbstractScreen screen);
	
}

AbstractScreen extends MainScreen to enable action.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.ui;

import blackberry.action.ActionListener;

import net.rim.device.api.ui.container.MainScreen;

/**
 * AbstractScreen
 * 
 * An abstraction of screen navigated by action outcomes.
 * 
 * @author Eki Baskoro
 *
 */
public abstract class AbstractScreen extends MainScreen {

	protected ActionListener actionListener = null;
	
	/**
	 * Register the action listener.
	 * 
	 * @param actionListener is the action listener
	 */
	public void setActionListener(ActionListener actionListener) {
		if (actionListener != null)
			this.actionListener = actionListener;
	}
	
	/**
	 * Notify action listener for action result
	 * 
	 * @param action is the action result
	 */
	protected void notifyActionListener(String action) {
		if (actionListener != null) {
			actionListener.execute(action, this);
		}
	}
}

Below is the LoginScreen that extends AbstractScreen.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook.ui;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Vector;

import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;

import net.rim.device.api.browser.field.BrowserContent;
import net.rim.device.api.browser.field.BrowserContentChangedEvent;
import net.rim.device.api.browser.field.Event;
import net.rim.device.api.browser.field.ExecutingScriptEvent;
import net.rim.device.api.browser.field.RedirectEvent;
import net.rim.device.api.browser.field.RenderingApplication;
import net.rim.device.api.browser.field.RenderingException;
import net.rim.device.api.browser.field.RenderingOptions;
import net.rim.device.api.browser.field.RenderingSession;
import net.rim.device.api.browser.field.RequestedResource;
import net.rim.device.api.browser.field.SetHeaderEvent;
import net.rim.device.api.browser.field.SetHttpCookieEvent;
import net.rim.device.api.browser.field.UrlRequestedEvent;
import net.rim.device.api.io.http.HttpProtocolConstants;
import net.rim.device.api.system.Application;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.Field;

import blackberry.facebook.FacebookException;
import blackberry.facebook.FacebookFacade;
import blackberry.ui.AbstractScreen;

/**
 * LoginScreen
 * 
 * Acts as an embedded browser to display the login screen.
 * In the future, cookies will be properly stored so login is performed only once
 * across all Facebook pages.
 * 
 * @author Eki Baskoro
 *
 */
public class LoginScreen extends AbstractScreen {

	private FacebookFacade facebookFacade = null;
	private RenderingApplicationImpl renderer = new RenderingApplicationImpl();
	private Vector cookies = new Vector();
	
	/**
	 * Create an instance of login screen.
	 * 
	 * @param facebookFacade the injected Facebook facade instance.
	 */
	public LoginScreen(FacebookFacade facebookFacade) {
		this.facebookFacade = facebookFacade;
		
		StringBuffer url = new StringBuffer()
			.append("http://m.facebook.com/login.php?")
			.append("api_key=").append(facebookFacade.getApplicationKey())
			.append("&connect_display=page")
			.append("&v=1.0")
			.append("&fbconnect=true")
			.append("&next=http://www.facebook.com/connect/prompt_permissions.php?api_key=").append(facebookFacade.getApplicationKey())
			.append("&ext_perm=read_stream,publish_stream,offline_access")
			.append("&next=http://www.facebook.com/connect/login_success.html?xxRESULTTOKENxx");
		
		(new FetchThread(url.toString())).start();
	}
	
	private void addCookies(String line) {
		Cookie[] cs = Cookie.parseCookies(line);
		
		for (int j = 0; j < cs.length; j ++) {
			addCookie(cs[j]);
		}
	}
	
	private void addCookie(Cookie cookie) {
		Enumeration cs = cookies.elements();
		
		while (cs.hasMoreElements()) {
			Cookie c = (Cookie)cs.nextElement();
			
			if (c.getName().equals(cookie.getName())) {
				cookies.removeElement(c);
				break;
			}
		}
		
		cookies.addElement(cookie);
	}
	
	private String returnCookie() {
		StringBuffer cookieValues = new StringBuffer();
		Cookie[] cs = new Cookie[cookies.size()];
		cookies.copyInto(cs);
		
		for (int i = 0; i < cs.length; i ++) {
			if (cs[i].getExpires() == null)
				continue;
			
			cookieValues.append(cs[i].getName()).append('=').append(cs[i].getValue()).append(';');
			
			if (i < cs.length-1)
				cookieValues.append(" ");
		}
		
    	return cookieValues.toString();
	}
	
	private void display(HttpConnection connection) {
		if (cookies.size() > 0) {
			try {
				connection.setRequestProperty(HttpProtocolConstants.HEADER_COOKIE, returnCookie());
			} catch (IOException e) {}
		}
		
		BrowserContent browserContent = renderer.getBrowserContent(connection);
		
		if (browserContent != null) {
			Field field = browserContent.getDisplayableContent();
			
			if (field != null) {
				try {
					String key = null;
					
					for (int i = 0; (key = connection.getHeaderFieldKey(i)) != null; i ++) {
						if (key.equalsIgnoreCase("set-cookie")) {
							addCookies(connection.getHeaderField(key));
						}
					}
				} catch (IOException e) {}
				
				synchronized (Application.getEventLock()) {
					deleteAll();
					add(field);
				}
			}
			
			try { browserContent.finishLoading(); }
			catch (RenderingException e) {}
		}
	}
	
	private class RenderingApplicationImpl implements RenderingApplication {
		
		private RenderingSession renderingSession = RenderingSession.getNewInstance();
		
		public RenderingApplicationImpl() {
			renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.JAVASCRIPT_ENABLED, true);
			renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.JAVASCRIPT_LOCATION_ENABLED, true);
			renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.WAP_MODE, true);
			renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.ENABLE_WML, true);
			renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.ENABLE_EMBEDDED_RICH_CONTENT, true);
			renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.ENABLE_CSS, true);
		}
		
		public BrowserContent getBrowserContent(HttpConnection connection) {
			try {
				return renderingSession.getBrowserContent(connection, this, null);
			} catch (RenderingException e) {
				return null;
			}
		}
		
		/**
	     * @see net.rim.device.api.browser.RenderingApplication#eventOccurred(net.rim.device.api.browser.Event)
	     */
	    public Object eventOccurred(Event event) {
	        int eventId = event.getUID();

	        switch (eventId) {

	            case Event.EVENT_URL_REQUESTED: {
	                UrlRequestedEvent urlRequestedEvent = (UrlRequestedEvent)event;
	                byte[] postData = urlRequestedEvent.getPostData();

	                if (postData != null) {
	                	(new FetchThread(urlRequestedEvent.getURL(), urlRequestedEvent.getPostData())).start();
	                } else {
	                	(new FetchThread(urlRequestedEvent.getURL())).start();
	                }
	                
	                break;
	            }
	            
	            case Event.EVENT_BROWSER_CONTENT_CHANGED: {
	                BrowserContentChangedEvent browserContentChangedEvent = (BrowserContentChangedEvent)event;
	                
	                if (browserContentChangedEvent.getSource() instanceof BrowserContent) {
	                    BrowserContent browserContent = (BrowserContent)browserContentChangedEvent.getSource();
	                    final String newUrl = browserContent.getURL();
	                    
	                    if (newUrl == null)
	                    	break;
	                    
	                    final int index = newUrl.indexOf("auth_token");
	                    
	                    if (index > -1) {
	                    	Application.getApplication().invokeLater(new Runnable() {
	                    		
	                    		public void run() {
	                    			String authToken = newUrl.substring(newUrl.indexOf('=', index)+1);
	                    			
	                    			try {
	                    				facebookFacade.getSession(authToken);
		                    			notifyActionListener("success");
	                    			} catch (FacebookException e) {
	                    				notifyActionListener("error");
	                    			}
	                    		}
	                    	});
	                    }
	                }

	                break;
	            }
	            
	            case Event.EVENT_REDIRECT: {
	            	RedirectEvent redirectEvent = (RedirectEvent)event;
	            	(new FetchThread(redirectEvent.getLocation())).start();
	            	break;
	            }
	            
	            case Event.EVENT_CLOSE:              // Close the appication
	                break;
	           
	            case Event.EVENT_SET_HEADER: {       // no cache support
	            	SetHeaderEvent setHeaderEvent = (SetHeaderEvent)event;
	            	break;
	            }
	            
	            case Event.EVENT_SET_HTTP_COOKIE: {
	            	SetHttpCookieEvent setHttpCookieEvent = (SetHttpCookieEvent)event;
	            	addCookies(setHttpCookieEvent.getCookie());
	            	break;
	            }
	            
	            case Event.EVENT_EXECUTING_SCRIPT: { // no progress bar is supported
	            	ExecutingScriptEvent executingScriptEvent = (ExecutingScriptEvent)event;
	            	break;
	            }
	            	
	            case Event.EVENT_HISTORY :           // no history support           
		        case Event.EVENT_FULL_WINDOW :       // no full window support
	            case Event.EVENT_STOP :              // no stop loading support
	            default :
	            	break;
	        }

	        return null;
	    }

	    /**
	     * @see net.rim.device.api.browser.RenderingApplication#getAvailableHeight(net.rim.device.api.browser.BrowserContent)

	     */
	    public int getAvailableHeight(BrowserContent browserField) {
	        return Display.getHeight();
	    }

	    /**
	     * @see net.rim.device.api.browser.RenderingApplication#getAvailableWidth(net.rim.device.api.browser.BrowserContent)

	     */
	    public int getAvailableWidth(BrowserContent browserField) {
	        return Display.getWidth();
	    }

	    /**
	     * @see net.rim.device.api.browser.RenderingApplication#getHistoryPosition(net.rim.device.api.browser.BrowserContent)

	     */
	    public int getHistoryPosition(BrowserContent browserField) {
	        // no history support
	        return 0;
	    }

	    /**
	     * @see net.rim.device.api.browser.RenderingApplication#getHTTPCookie(java.lang.String)
	     */
	    public String getHTTPCookie(String url) {
	    	return returnCookie();
	    }

	    /**
	     * @see net.rim.device.api.browser.RenderingApplication#getResource(net.rim.device.api.browser.RequestedResource,
	     *      net.rim.device.api.browser.BrowserContent)
	     */
	    public HttpConnection getResource(RequestedResource resource, BrowserContent referrer) {

	        if (resource == null) {
	            return null;
	        }

	        if (resource.isCacheOnly()) {
	            return null; // no cache support
	        }

	        final String url = resource.getUrl();

	        if (url == null)
	            return null;

	        if (referrer == null) {
	        	try { return (HttpConnection)Connector.open(url); }
	        	catch (IOException e) {}
	        } else {
	        	(new ResourceFetchThread(resource, referrer)).start();
	        }

	        return null;
	    }

	    /**
	     * @see net.rim.device.api.browser.RenderingApplication#invokeRunnable(java.lang.Runnable)
	     */
	    public void invokeRunnable(Runnable runnable) {
	        (new Thread(runnable)).run();
	    }
	}
	
	/**
	 * Do GET or POST
	 * 
	 */
	private class FetchThread extends Thread {
		
		private String absoluteUrl = null;
		private String method = HttpConnection.GET;
		private byte[] data = null;
		
		public FetchThread(String absoluteUrl) {
			this.absoluteUrl = absoluteUrl;
		}
		
		public FetchThread(String absoluteUrl, String method) {
			this.absoluteUrl = absoluteUrl;
			this.method = method;
		}
		
		public FetchThread(String absoluteUrl, byte[] data) {
			this.absoluteUrl = absoluteUrl;
			this.method = HttpConnection.POST;
			this.data = data;
		}
		
		public void run() {
			HttpConnection connection = null;
			
			try {
				connection = (HttpConnection)Connector.open(absoluteUrl);
				connection.setRequestProperty("x-rim-gw-properties", "16.10");
				connection.setRequestProperty("x-rim-transcode-content", "*/*");
				connection.setRequestProperty("x-rim-accept-encoding", "yk;v=3;m=384");
				connection.setRequestProperty("x-wap-profile", "\"http://www.blackberry.net/go/mobile/profiles/uaprof/8320/4.5.0.rdf\"");
				connection.setRequestProperty("profile", "http://www.blackberry.net/go/mobile/profiles/uaprof/8320/4.5.0.rdf");
				connection.setRequestProperty("User-Agent", "BlackBerry8320/4.5.0.44 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/-1");
				connection.setRequestProperty("Accept", "application/vnd.rim.html,text/html,application/xhtml+xml,application/vnd.wap.xhtml+xml,application/vnd.wap.wmlc;q=0.9,application/vnd.wap.wmlscriptc;q=0.7,text/vnd.wap.wml;q=0.7,text/vnd.sun.j2me.app-descriptor,image/vnd.rim.png,image/jpeg,application/x-vnd.rim.pme.b,application/vnd.rim.ucs,image/gif;anim=1,application/vnd.rim.css;v=1,text/css;media=screen,*/*;q=0.5");
				connection.setRequestProperty("x-rim-original-accept", "application/vnd.rim.html,text/html,application/xhtml+xml,application/vnd.wap.xhtml+xml,application/vnd.wap.wmlc;q=0.9,application/vnd.wap.wmlscriptc;q=0.7,text/vnd.wap.wml;q=0.7,text/vnd.sun.j2me.app-descriptor,image/vnd.rim.png,image/jpeg,application/x-vnd.rim.pme.b,application/vnd.rim.ucs,image/gif;anim=1,application/vnd.rim.css;v=1,text/css;media=screen,*/*;q=0.");
				
				if (method == HttpConnection.POST) {
					connection.setRequestMethod(HttpConnection.POST);
					connection.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_TYPE, HttpProtocolConstants.CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED);
					connection.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_LENGTH, String.valueOf(data.length));
					OutputStream os = connection.openOutputStream();
					os.write(data);
				} else if (method == HttpConnection.GET) {
					connection.setRequestMethod(HttpConnection.GET);
				}
				
				display(connection);
			} catch (IOException e) {
			} finally {
				if (connection != null) {
					try { connection.close(); }
					catch (IOException e) {}
				}
			}
		}
	}
	
	/**
	 * Fetch requested resources.
	 * 
	 * @author Eki Baskoro
	 *
	 */
	private class ResourceFetchThread extends Thread {
		
		private RequestedResource requestedResource = null;
		private BrowserContent browserContent = null;
		
		public ResourceFetchThread(RequestedResource requestedResource, BrowserContent browserContent) {
			this.requestedResource = requestedResource;
			this.browserContent = browserContent;
		}
		
		public void run() {
			HttpConnection connection = null;
			
			try {
				connection = (HttpConnection)Connector.open(requestedResource.getUrl());
				connection.setRequestProperty("x-rim-gw-properties", "16.10");
				connection.setRequestProperty("x-rim-transcode-content", "*/*");
				connection.setRequestProperty("x-rim-accept-encoding", "yk;v=3;m=384");
				connection.setRequestProperty("x-wap-profile", "\"http://www.blackberry.net/go/mobile/profiles/uaprof/8320/4.5.0.rdf\"");
				connection.setRequestProperty("profile", "http://www.blackberry.net/go/mobile/profiles/uaprof/8320/4.5.0.rdf");
				connection.setRequestProperty("User-Agent", "BlackBerry8320/4.5.0.44 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/-1");
				connection.setRequestProperty("Accept", "application/vnd.rim.html,text/html,application/xhtml+xml,application/vnd.wap.xhtml+xml,application/vnd.wap.wmlc;q=0.9,application/vnd.wap.wmlscriptc;q=0.7,text/vnd.wap.wml;q=0.7,text/vnd.sun.j2me.app-descriptor,image/vnd.rim.png,image/jpeg,application/x-vnd.rim.pme.b,application/vnd.rim.ucs,image/gif;anim=1,application/vnd.rim.css;v=1,text/css;media=screen,*/*;q=0.5");
				connection.setRequestProperty("x-rim-original-accept", "application/vnd.rim.html,text/html,application/xhtml+xml,application/vnd.wap.xhtml+xml,application/vnd.wap.wmlc;q=0.9,application/vnd.wap.wmlscriptc;q=0.7,text/vnd.wap.wml;q=0.7,text/vnd.sun.j2me.app-descriptor,image/vnd.rim.png,image/jpeg,application/x-vnd.rim.pme.b,application/vnd.rim.ucs,image/gif;anim=1,application/vnd.rim.css;v=1,text/css;media=screen,*/*;q=0.");
				requestedResource.setHttpConnection(connection);
				browserContent.resourceReady(requestedResource);
			} catch (IOException e) {
			} finally {
				if (connection != null) {
					try { connection.close(); }
					catch (IOException e) {}
				}
			}
		}
	}
	
}

Cookie class

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook.ui;

import java.util.Vector;

/**
 * Cookie
 * 
 * Represents a browser cookie.
 * 
 * @author Eki Baskoro
 *
 */
public class Cookie {

	private String name;
	private String value;
	private String expires;
	private String path = "/";
	private String domain;
	private boolean httpOnly = false;
	
	public static Cookie[] parseCookies(String line) {
		Vector cookies = new Vector();
		int startIndex = 0;
		
		do {
			int stopIndex = line.indexOf(',', startIndex);
			stopIndex = (stopIndex > -1)? stopIndex : line.length();
			
			if (line.charAt(stopIndex-4) == '=') {
				stopIndex = line.indexOf(',', stopIndex+1);
				stopIndex = (stopIndex > -1)? stopIndex : line.length();
			}
			
			cookies.addElement(parse(line.substring(startIndex, stopIndex)));
			startIndex = stopIndex + 1;
		} while (startIndex < line.length());
		
		Cookie[] cookiesArray = new Cookie[cookies.size()];
		cookies.copyInto(cookiesArray);
		
		return cookiesArray;
	}
	
	public static Cookie parse(String line) {
		String name = null;
		String value = null;
		String expires = null;
		String path = null;
		String domain = null;
		boolean httpOnly = false;
		int startIndex = 0;
		
		do {
			int stopIndex = line.indexOf(';', startIndex);
			stopIndex = (stopIndex > -1)? stopIndex : line.length();
			String nvpair = line.substring(startIndex, stopIndex);
			int index = nvpair.indexOf('=');
			
			if (index > -1) {
				String n = nvpair.substring(0, index).trim();
				String v = nvpair.substring(index+1);
				
				if (n.equalsIgnoreCase("expires")) {
					expires = v;
				} else if (n.equalsIgnoreCase("path")) {
					path = v;
				} else if (n.equalsIgnoreCase("domain")) {
					domain = v;
				} else {
					name = n;
					value = v;
				}
			} else if (nvpair.trim().equalsIgnoreCase("httponly")){
				httpOnly = true;
			}
			
			startIndex = stopIndex + 1;
		} while (startIndex < line.length());
		
		return new Cookie(name, value, expires, path, domain, httpOnly);
	}
	
	/**
	 * Create an instance of cookie.
	 * 
	 * @param name cookie's name.
	 * @param value cookie's value.
	 * @param expires expiration.
	 * @param path applicable path.
	 * @param domain applicable domain.
	 * @param httpOnly only for HTTP requests.
	 */
	public Cookie(String name, String value, String expires, String path, String domain, boolean httpOnly) {
		this.name = name;
		this.value = value;
		this.expires = expires;
		this.path = path;
		this.domain = domain;
		this.httpOnly = httpOnly;
	}
	
	public String getName() {
		return name;
	}
	
	public String getValue() {
		return value;
	}
	
	public String getExpires() {
		return expires;
	}
	
	public String getPath() {
		return path;
	}
	
	public String getDomain() {
		return domain;
	}
	
	public boolean isHttpOnly() {
		return httpOnly;
	}
	
}

Next is the FacebookFacade class.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;

import net.rim.blackberry.api.browser.URLEncodedPostData;
import net.rim.device.api.collection.util.SortedReadableList;
import net.rim.device.api.crypto.MD5Digest;
import net.rim.device.api.io.http.HttpProtocolConstants;
import net.rim.device.api.util.Persistable;
import net.rim.device.api.util.StringComparator;

import org.json.me.JSONArray;
import org.json.me.JSONObject;
import org.json.me.JSONTokener;

/**
 * FacebookFacade
 * 
 * Encapsulates Facebook REST server.
 * 
 * @author Eki Baskoro
 *
 */
public class FacebookFacade implements Persistable {

	public static final String EXT_PERM_EMAIL = "email";
	public static final String EXT_PERM_READ_STREAM = "read_stream";
	public static final String EXT_PERM_PUBLISH_STREAM = "publish_stream";
	public static final String EXT_PERM_OFFLINE_ACCESS = "offline_access";
	public static final String EXT_PERM_XMPP_LOGIN = "xmpp_login";
	
	private String applicationKey = null;
	private String applicationSecret = null;
	private String sessionKey = null;
	private String sessionSecret = null;
	private String logMessage = null;
	
	/**
	 * Default constructor.
	 * 
	 * @param applicationKey the application key.
	 * @param applicationSecret the application secret.
	 */
	public FacebookFacade(String applicationKey, String applicationSecret) {
		this.applicationKey = applicationKey;
		this.applicationSecret = applicationSecret;
	}
	
	/**
	 * Obtain the application key.
	 * 
	 * @return the application key.
	 */
	public String getApplicationKey() {
		return applicationKey;
	}
	
	/**
	 * Obtain the session key.
	 * 
	 * @return the session key.
	 */
	public String getSessionKey() {
		return sessionKey;
	}
	
	/**
	 * Obtain the session secret.
	 * 
	 * @return the session secret.
	 */
	public String getSessionSecret() {
		return sessionSecret;
	}
	
	/**
	 * Obtain the log message.
	 * Currently it is not implemented.
	 * 
	 * @return the log message.
	 */
	public String getLogMessage() {
		return logMessage;
	}
	
	/**
	 * Check whether the facade currently holds a session.
	 * In the future, this will be replaced by querying the REST server directly.
	 * 
	 * @return true if session key exists or false otherwise.
	 */
	public boolean hasSession() {
		return (sessionKey != null);
	}
	
	/**
	 * Obtain a session given an authorisation token.
	 * 
	 * @param token the authorisation token as returned from the LoginScreen.
	 * @throws FacebookException when an error occurs.
	 */
	public void getSession(String token) throws FacebookException {
		Hashtable arguments = new Hashtable();
		arguments.put("method", "auth.getSession");
		arguments.put("auth_token", token);
		
		try {
			JSONObject response = new JSONObject(new JSONTokener(sendRequest(arguments)));
			sessionKey = response.getString("session_key");
			sessionSecret = response.getString("secret");
		} catch (Exception e) {
			throw new FacebookException(e.getMessage());
		}
	}
	
	/**
	 * Obtain the UID of the currently logged in user.
	 * 
	 * @return the UID
	 * @throws FacebookException when an error occurs.
	 */
	public long getLoggedInUserId() throws FacebookException {
		Hashtable arguments = new Hashtable();
		arguments.put("method", "users.getLoggedInUser");
		
		try {
			return Long.parseLong(sendRequest(arguments));
		} catch (Exception e) {
			throw new FacebookException(e.getMessage());
		}
	}
	
	/**
	 * Obtain an instance of User given its UID.
	 * 
	 * @param uid the User's UID.
	 * @return an instance of User given its UID.
	 * @throws FacebookException when an error occurs.
	 */
	public User getUser(long uid) throws FacebookException {
		try {
			return getUsers(new long[] {uid})[0];
		} catch (Exception e) {
			logMessage = e.getMessage();
			throw new FacebookException(e.getMessage());
		}
	}
	
	/**
	 * Obtain an array of User instances given an array of UIDs.
	 * 
	 * @param uids an array of UIDs.
	 * @return an array of corresponding User instances.
	 * @throws FacebookException when an error occurs.
	 */
	public User[] getUsers(long[] uids) throws FacebookException {
		User[] result = null;
		StringBuffer uidsArgument = new StringBuffer();
		
		for (int i = 0; i < uids.length; i ++) {
			uidsArgument.append(uids[i]);
			
			if (i < uids.length-1)
				uidsArgument.append(",");
		}
		
		Hashtable arguments = new Hashtable();
		arguments.put("method", "users.getInfo");
		arguments.put("uids", uidsArgument.toString());
		arguments.put("fields", "first_name,last_name,name,pic,pic_small,status");
		
		try {
			JSONArray users = new JSONArray(new JSONTokener(sendRequest(arguments)));
			result = new User[users.length()];
			
			for (int i = 0; i < users.length(); i ++) {
				JSONObject user = users.getJSONObject(i);
				
				long uid = user.getLong("uid");
				String username = user.optString("username");
				String firstName = user.getString("first_name");
				String lastName = user.getString("last_name");
				String name = user.getString("name");
				String pictureUrl = user.optString("pic");
				String pictureSmallUrl = user.optString("pic_small");
				String status = "";
				
				if (!user.isNull("status")) {
					status = user.getJSONObject("status").getString("message");
				}
				
				result[i] = new User(uid, username, firstName, lastName, name, pictureUrl, pictureSmallUrl, status);
			}
		} catch (Exception e) {
			logMessage = e.getMessage();
			throw new FacebookException(e.getMessage());
		}
		
		return result;
	}
	
	/**
	 * Obtain friends as an array of User instances.
	 * 
	 * @return an array of User instances.
	 * @throws FacebookException when an error occurs.
	 */
	public User[] getFriends() throws FacebookException {
		User[] friends = null;
		Hashtable arguments = new Hashtable();
		arguments.put("method", "friends.get");
		
		try {
			JSONArray friendUids = new JSONArray(sendRequest(arguments));
			long[] uids = new long[friendUids.length()];
			
			for (int i = 0; i < friendUids.length(); i ++) {
				uids[i] = friendUids.getLong(i);
			}
			
			friends = getUsers(uids);
		} catch (Exception e) {
			throw new FacebookException(e.getMessage());
		}
		
		return friends;
	}
	
	/**
	 * Obtain the user's stream limited to limit numbers of post.
	 * 
	 * @param limit the maximum number of posts to retrieve.
	 * @return an instance of Stream.
	 * @throws FacebookException when an error occurs.
	 */
	public Stream getStream(int limit) throws FacebookException {
		Stream stream = new Stream();
		Hashtable arguments = new Hashtable();
		arguments.put("method", "stream.get");
		arguments.put("limit", String.valueOf(limit));
		
		try {
			JSONObject result = new JSONObject(sendRequest(arguments));
			
			if (result.has("posts")) {
				JSONArray postsArray = result.getJSONArray("posts");
				Post[] posts = new Post[postsArray.length()];
				
				for (int i = 0; i < postsArray.length(); i ++) {
					JSONObject postObject = postsArray.getJSONObject(i);
					String id = postObject.getString("post_id");
					long actorId = postObject.getLong("actor_id");
					Date createdDate = new Date(postObject.getLong("created_time"));
					String message = postObject.getString("message");
					int commentsCount = postObject.getJSONObject("comments").getInt("count");
					
					posts[i] = new Post(id, actorId, createdDate, message, commentsCount);
				}
				
				stream.setPosts(posts);
			}
		} catch (Exception e) {
			throw new FacebookException(e.getMessage());
		}
		
		return stream;
	}
	
	/**
	 * Set the user's status.
	 * 
	 * @param status the new status.
	 */
	public void updateStatus(String status) {
		Hashtable arguments = new Hashtable();
		arguments.put("method", "status.set");
		arguments.put("status", status);
		sendRequest(arguments);
	}
	
	/**
	 * Publish a stream to the user's wall.
	 * 
	 * @param privacy an instance of privacy.
	 * @param message message
	 * @param attachment an instance of attachment.
	 * @param actionLinks array of action link instances.
	 * @throws FacebookException when an error occurs.
	 */
	public void publishStream(Privacy privacy, String message, Attachment attachment, ActionLink[] actionLinks) throws FacebookException {
		publishStream(null, null, privacy, message, attachment, actionLinks);
	}
	
	/**
	 * Publish a stream to the specified target ID.
	 * 
	 * @param uid the user or page ID.
	 * @param targetId the target ID to post the stream to.
	 * @param privacy an instance of privacy.
	 * @param message message
	 * @param attachment an instance of attachment.
	 * @param actionLinks array of action link instances.
	 * @throws FacebookException when an error occurs.
	 */
	public void publishStream(String uid, String targetId, Privacy privacy, String message, Attachment attachment, ActionLink[] actionLinks) throws FacebookException {
		Hashtable arguments = new Hashtable();
		arguments.put("method", "stream.publish");
		arguments.put("message", message);
		
		if (uid != null) {
			arguments.put("uid", uid);
		}
		
		if (targetId != null) {
			arguments.put("target_id", targetId);
		}
		
		try {
			JSONObject privacyObject = new JSONObject();
			
			privacyObject.put("value", privacy.getValue());
			
			if (privacy instanceof CustomPrivacy) {
				CustomPrivacy customPrivacy = (CustomPrivacy)privacy;
				privacyObject.put("friends", customPrivacy.getFriends());
				privacyObject.put("networks", customPrivacy.getNetworks());
				
				if (customPrivacy.getFriends().equals(CustomPrivacy.SOME_FRIENDS)) {
					privacyObject.put("allow", customPrivacy.getAllow());
					privacyObject.put("deny", customPrivacy.getDeny());
				}
			}
			
			arguments.put("privacy", privacyObject.toString());
			
			if (attachment != null) {
				JSONObject attachmentObject = new JSONObject();
				
				attachmentObject.put("name", attachment.getName());
				attachmentObject.put("href", attachment.getHyperlink());
				attachmentObject.put("caption", attachment.getCaption());
				attachmentObject.put("description", attachment.getDescription());
				
				if (attachment.getMedias() != null) {
					Media[] medias = attachment.getMedias();
					JSONArray mediaArray = new JSONArray();
					
					for (int i = 0; i < medias.length; i ++) {
						JSONObject mediaObject = new JSONObject();
						Media media = medias[i];
						
						if (media instanceof ImageMedia) {
							ImageMedia imageMedia = (ImageMedia)media;
							
							mediaObject.put("type", imageMedia.getType());
							mediaObject.put("src", imageMedia.getSource());
							mediaObject.put("href", imageMedia.getHyperlink());
						} else if (media instanceof FlashMedia) {
							FlashMedia flashMedia = (FlashMedia)media;
							
							mediaObject.put("type", flashMedia.getType());
							mediaObject.put("swfsrc", flashMedia.getSource());
							mediaObject.put("imgsrc", flashMedia.getImageSource());
							
							if (flashMedia.getWidth() > 0) {
								mediaObject.put("width", flashMedia.getWidth());
							}
							
							if (flashMedia.getHeight() > 0) {
								mediaObject.put("height", flashMedia.getHeight());
							}
							
							if (flashMedia.getExpandedWidth() > 0) {
								mediaObject.put("expanded_width", flashMedia.getExpandedWidth());
							}
							
							if (flashMedia.getExpandedHeight() > 0) {
								mediaObject.put("expanded_height", flashMedia.getExpandedHeight());
							}
						} else if (media instanceof Mp3Media) {
							Mp3Media mp3Media = (Mp3Media)media;
							
							mediaObject.put("type", mp3Media.getType());
							mediaObject.put("src", mp3Media.getSource());
							mediaObject.put("title", mp3Media.getTitle());
							mediaObject.put("artist", mp3Media.getArtist());
							mediaObject.put("album", mp3Media.getAlbum());
						}
						
						mediaArray.put(mediaObject);
					}
					
					attachmentObject.put("media", mediaArray);
				}
				
				arguments.put("attachment", attachmentObject.toString());
			}
			
			if (actionLinks != null) {
				JSONArray actionLinkArray = new JSONArray();
				
				for (int i = 0; i < actionLinks.length; i ++) {
					ActionLink actionLink = actionLinks[i];
					JSONObject actionLinkObject = new JSONObject();
					actionLinkObject.put("text", actionLink.getText());
					actionLinkObject.put("href", actionLink.getHyperlink());
					actionLinkArray.put(actionLinkObject);
				}
				
				arguments.put("action_links", actionLinkArray.toString());
			}
			
			String response = sendRequest(arguments);
			
			if (response.indexOf("error") > -1) {
				JSONObject object = new JSONObject(response);
				throw new FacebookException(object.getString("error_code"));
			}
		} catch (FacebookException e) {
			throw e;
		} catch (Exception e) {
			throw new FacebookException(e.getMessage());
		}
	}
	
	/**
	 * user.hasAppPermission
	 * 
	 * @param extendedPermission extended permission to check
	 * @return true if the logged in user has granted extendedPermission
	 * @throws FacebookException
	 */
	public boolean hasAppPermission(String extendedPermission) throws FacebookException {
		if (!extendedPermission.equals(EXT_PERM_EMAIL)
				&& !extendedPermission.equals(EXT_PERM_OFFLINE_ACCESS)
				&& !extendedPermission.equals(EXT_PERM_PUBLISH_STREAM)
				&& !extendedPermission.equals(EXT_PERM_READ_STREAM)
				&& !extendedPermission.equals(EXT_PERM_XMPP_LOGIN))
			throw new FacebookException("Invalid extended permission");
		
		Hashtable arguments = new Hashtable();
		arguments.put("method", "users.hasAppPermission");
		arguments.put("ext_perm", extendedPermission);
		
		try {
			return (sendRequest(arguments).equals("1"));
		} catch (Exception e) {
			throw new FacebookException(e.getMessage());
		}
	}
	
	private String sendRequest(Hashtable data) {
		HttpConnection connection = null;
		String response = null;
		
		try {
			if (sessionKey != null) {
				data.put("session_key", sessionKey);
			}
			
			data.put("api_key", applicationKey);
			data.put("call_id", String.valueOf(System.currentTimeMillis()));
			data.put("v", "1.0");
			data.put("format", "JSON");
			data.put("sig", getSignature(data, (sessionSecret != null)? sessionSecret : applicationSecret));
			
			URLEncodedPostData encoder = new URLEncodedPostData(null, false);
			Enumeration keysEnum = data.keys();
			
			while (keysEnum.hasMoreElements()) {
				String key = (String)keysEnum.nextElement();
				String val = (String)data.get(key);
				encoder.append(key, val);
			}
			
			connection = (HttpConnection)Connector.open("http://api.facebook.com/restserver.php;deviceSide=true");
			connection.setRequestMethod(HttpConnection.POST);
			connection.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_TYPE, HttpProtocolConstants.CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED);
			connection.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_LENGTH, String.valueOf(encoder.getBytes().length));
			
			OutputStream os = connection.openOutputStream();
			os.write(encoder.getBytes());
			
			if (connection.getResponseCode() == HttpConnection.HTTP_OK) {
				StringBuffer buffer = new StringBuffer();
				InputStream is = connection.openInputStream();
				int c;
				
				while ((c = is.read()) != -1) {
					buffer.append((char)c);
				}
				
				is.close();
				response = buffer.toString();
			}
		} catch (Exception e) {
		} finally {
			if (connection != null) {
				try { connection.close(); }
				catch (IOException e) {}
			}
		}
		
		return response;
	}
	
	private static String getSignature(Hashtable arguments, String secret) {
		try {
			SortedReadableList keysList = new SortedReadableList(StringComparator.getInstance(true));
			keysList.loadFrom(arguments.keys());
			keysList.sort();
			StringBuffer requestString = new StringBuffer();
			
			for (int i = 0; i < keysList.size(); i ++) {
				String key = (String)keysList.getAt(i);
				String val = (String)arguments.get(key);
				requestString.append(key + "=" + val);
			}
			
			requestString.append(secret);
			
			MD5Digest digest = new MD5Digest();
			digest.update(requestString.toString().getBytes("iso-8859-1"), 0, requestString.length());
			byte[] digestResult = digest.getDigest();
			
			return convertToHex(digestResult);
		} catch (IOException e) {
			return null;
		}
	}
	
	private static String convertToHex(byte[] data) {
        StringBuffer buf = new StringBuffer();
        
        for (int i = 0; i < data.length; i++) {
            int halfbyte = (data[i] >>> 4) & 0x0F;
            int two_halfs = 0;
            
            do {
                if ((0 <= halfbyte) && (halfbyte <= 9))
                    buf.append((char) ('0' + halfbyte));
                else
                    buf.append((char) ('a' + (halfbyte - 10)));
                
                halfbyte = data[i] & 0x0F;
            } while (two_halfs++ < 1);
        }
        
        return buf.toString();
    }
	
}

Next is the FacebookException class. This class encapsulates any error related to the Facade.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

/**
 * FacebookException
 * 
 * @author Eki Baskoro
 *
 */
public class FacebookException extends Exception {

	/**
	 * 
	 */
	public FacebookException() {
		super();
	}

	/**
	 * @param message
	 */
	public FacebookException(String message) {
		super(message);
	}

}

Below is the User class representing a Facebook User.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

/**
 * User
 * 
 * Represents a Facebook User.
 * 
 * @author Eki Baskoro
 *
 */
public class User {

	private long uid;
	private String username;
	private String firstName;
	private String lastName;
	private String name;
	private String pictureUrl;
	private String pictureSmallUrl;
	private String status;
	
	/**
	 * Create an instance of user.
	 * 
	 * @param uid the user ID.
	 * @param username the username.
	 * @param firstName the first name.
	 * @param lastName the last name.
	 * @param name the full name.
	 * @param pictureUrl the profile picture URL.
	 * @param pictureSmallUrl the small profile picture URL.
	 * @param status the current status.
	 */
	public User(long uid, String username, String firstName, String lastName, String name, String pictureUrl, String pictureSmallUrl, String status) {
		this.uid = uid;
		this.username = username;
		this.firstName = firstName;
		this.lastName = lastName;
		this.name = name;
		this.pictureUrl = pictureUrl;
		this.pictureSmallUrl = pictureSmallUrl;
		this.status = status;
	}

	/**
	 * Obtain the user ID.
	 * 
	 * @return the ID.
	 */
	public long getUid() {
		return uid;
	}
	
	/**
	 * Obtain the username.
	 * 
	 * @return the username.
	 */
	public String getUsername() {
		return username;
	}
	
	/**
	 * Obtain the first name.
	 * 
	 * @return the first name.
	 */
	public String getFirstName() {
		return firstName;
	}
	
	/**
	 * Obtain the last name.
	 * 
	 * @return the last name.
	 */
	public String getLastName() {
		return lastName;
	}
	
	/**
	 * Obtain the full name.
	 * 
	 * @return the full name.
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Obtain the profile picture URL.
	 * 
	 * @return the URL.
	 */
	public String getPictureUrl() {
		return pictureUrl;
	}
	
	/**
	 * Obtain the small profile picture URL.
	 * 
	 * @return the URL.
	 */
	public String getPictureSmallUrl() {
		return pictureSmallUrl;
	}
	
	/**
	 * Obtain the current status.
	 * 
	 * @return the status.
	 */
	public String getStatus() {
		return status;
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		return getName();
	}
	
}

Next is the Post class.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

import java.util.Date;

/**
 * Post
 * 
 * Represents a post in a stream.
 * 
 * @author Eki Baskoro
 *
 */
public class Post {

	private String id;
	private long actorId;
	private Date createdDate;
	private String message;
	private int commentsCount;
	
	/**
	 * Create an instance of post.
	 * 
	 * @param id the post ID.
	 * @param actorId the actor ID.
	 * @param createdDate the created date.
	 * @param message the message.
	 * @param commentsCount the number of comments.
	 */
	public Post(String id, long actorId, Date createdDate, String message, int commentsCount) {
		this.id = id;
		this.actorId = actorId;
		this.createdDate = createdDate;
		this.message = message;
		this.commentsCount = commentsCount;
	}
	
	/**
	 * Obtain the post ID.
	 * 
	 * @return the ID.
	 */
	public String getId() {
		return id;
	}
	
	/**
	 * Obtain the actor ID.
	 * 
	 * @return the ID.
	 */
	public long getActorId() {
		return actorId;
	}
	
	/**
	 * Obtain the created date.
	 * 
	 * @return the date.
	 */
	public Date getCreatedDate() {
		return createdDate;
	}
	
	/**
	 * Obtain the message.
	 * 
	 * @return the message.
	 */
	public String getMessage() {
		return message;
	}
	
	/**
	 * Obtain the number of comments.
	 * 
	 * @return the number.
	 */
	public int getCommentsCount() {
		return commentsCount;
	}
	
}

Next is the Stream class.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

/**
 * Stream
 * 
 * Represents a stream.
 * 
 * @author Eki Baskoro
 *
 */
public class Stream {

	private Post[] posts = null;
	
	/**
	 * Default constructor.
	 * 
	 */
	public Stream() {
	}
	
	/**
	 * Obtain an array of post instances.
	 * 
	 * @return the array.
	 */
	public Post[] getPosts() {
		return posts;
	}
	
	/**
	 * Change the array of post instances.
	 * 
	 * @param posts the new array.
	 */
	public void setPosts(Post[] posts) {
		this.posts = posts;
	}
	
}

Below is the Privacy class.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

/**
 * Privacy
 * 
 * Represents a privacy setting for stream publishing.
 * 
 * @author Eki Baskoro
 *
 */
public class Privacy {

	public static final String EVERYONE = "EVERYONE";
	public static final String ALL_FRIENDS = "ALL_FRIENDS";
	public static final String NETWORKS_FRIENDS = "NETWORKS_FRIENDS";
	public static final String FRIENDS_OF_FRIENDS = "FRIENDS_OF_FRIENDS";
	
	protected String value;
	
	/**
	 * Default constructor.
	 * 
	 */
	public Privacy() {
		this.value = EVERYONE;
	}
	
	/**
	 * Create a privacy instance given its value.
	 * 
	 * @param value the privacy value.
	 * @throws FacebookException when the value of invalid value.
	 */
	public Privacy(String value) throws FacebookException {
		if (!value.equals(EVERYONE)
				&& !value.equals(ALL_FRIENDS)
				&& !value.equals(NETWORKS_FRIENDS)
				&& !value.equals(FRIENDS_OF_FRIENDS))
			throw new FacebookException("Invalid value for privacy value");
		
		this.value = value;
	}
	
	/**
	 * Obtain the privacy value.
	 * 
	 * @return the value.
	 */
	public String getValue() {
		return value;
	}
	
	/**
	 * Change the privacy value.
	 * 
	 * @param value the new value.
	 * @throws FacebookException when the value of invalid value.
	 */
	public void setValue(String value) throws FacebookException {
		if (!value.equals(EVERYONE)
				&& !value.equals(ALL_FRIENDS)
				&& !value.equals(NETWORKS_FRIENDS)
				&& !value.equals(FRIENDS_OF_FRIENDS))
			throw new FacebookException("Invalid value for privacy value");
		
		this.value = value;
	}
	
}

And also the CustomPrivacy class.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

import java.util.Enumeration;
import java.util.Vector;

/**
 * CustomPrivacy
 * 
 * Represents a custom privacy setting for stream publishing.
 * 
 * @author Eki Baskoro
 *
 */
public class CustomPrivacy extends Privacy {

	public static final String SOME_FRIENDS = "SOME_FRIENDS";
	public static final String SELF = "SELF";
	public static final String NO_FRIENDS = "NO_FRIENDS";
	
	private static final String CUSTOM = "CUSTOM";
	
	private String friends = EVERYONE;
	private Vector networks = new Vector();
	private Vector allow = new Vector();
	private Vector deny = new Vector();
	
	/**
	 * Default constructor.
	 * 
	 */
	public CustomPrivacy() {
		this.value = CUSTOM;
	}
	
	/**
	 * Create a custom privacy.
	 * 
	 * @param friends the privacy value for friends.
	 */
	public CustomPrivacy(String friends) {
		this.value = CUSTOM;
		this.friends = friends;
	}
	
	/**
	 * Obtain the privacy value for friends.
	 * 
	 * @return the value.
	 */
	public String getFriends() {
		return friends;
	}
	
	/**
	 * Change the privacy value for friends.
	 * 
	 * @param friends the new value.
	 */
	public void setFriends(String friends) {
		this.friends = friends;
	}
	
	/**
	 * Obtain the list of allowed networks.
	 * 
	 * @return comma-separated list of allowed networks.
	 */
	public String getNetworks() {
		if (networks.size() == 0)
			return "1";
		
		StringBuffer buffer = new StringBuffer();
		Enumeration e = networks.elements();
		
		while (e.hasMoreElements()) {
			buffer.append(e.nextElement());
			
			if (e.hasMoreElements())
				buffer.append(',');
		}
		
		return buffer.toString();
	}
	
	/**
	 * Add a network ID to the list of allowed networks.
	 * 
	 * @param networkId the network ID to add.
	 */
	public void addNetwork(int networkId) {
		networks.addElement(new Integer(networkId));
	}
	
	/**
	 * Remove a network ID from the list of allowed networks.
	 * 
	 * @param networkId the network ID to remove.
	 */
	public void removeNetwork(int networkId) {
		networks.removeElement(new Integer(networkId));
	}
	
	/**
	 * Obtain the list of allowed friends.
	 * 
	 * @return comma-separated list of allowed friends.
	 */
	public String getAllow() {
		StringBuffer buffer = new StringBuffer();
		Enumeration e = allow.elements();
		
		while (e.hasMoreElements()) {
			buffer.append(e.nextElement());
			
			if (e.hasMoreElements())
				buffer.append(',');
		}
		
		return buffer.toString();
	}
	
	/**
	 * Add a user ID to the list of allowed users.
	 * 
	 * @param uid the user ID to add.
	 */
	public void addAllow(int uid) {
		allow.addElement(new Integer(uid));
	}
	
	/**
	 * Remove a user ID from the list of allowed users.
	 * 
	 * @param uid the user ID to remove.
	 */
	public void removeAllow(int uid) {
		allow.removeElement(new Integer(uid));
	}
	
	/**
	 * Obtain the list of denied friends.
	 * 
	 * @return comma-separated list of denied friends.
	 */
	public String getDeny() {
		StringBuffer buffer = new StringBuffer();
		Enumeration e = deny.elements();
		
		while (e.hasMoreElements()) {
			buffer.append(e.nextElement());
			
			if (e.hasMoreElements())
				buffer.append(',');
		}
		
		return buffer.toString();
	}
	
	/**
	 * Add a user ID to the list of denied users.
	 * 
	 * @param uid the user ID to add.
	 */
	public void addDeny(int uid) {
		deny.addElement(new Integer(uid));
	}
	
	/**
	 * Remove a user ID from the list of denied users.
	 * 
	 * @param uid the user ID to remove.
	 */
	public void removeDeny(int uid) {
		deny.removeElement(new Integer(uid));
	}
	
}

Abstract Media class.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

/**
 * Media
 * 
 * Represents an abstraction of differing media type. ie. image, flash and mp3
 * 
 * @author Eki Baskoro
 *
 */
public abstract class Media {

	public static final String IMAGE = "image";
	public static final String FLASH = "flash";
	public static final String MP3 = "mp3";
	
	protected String type;
	protected String source;
	
	/**
	 * Obtain the media type.
	 * 
	 * @return the type.
	 */
	public String getType() {
		return type;
	}
	
	/**
	 * Change the media type.
	 * 
	 * @param type the new type.
	 * @throws FacebookException when type is of invalid value.
	 */
	public void setType(String type) throws FacebookException {
		if (!type.equals(IMAGE)
				&& !type.equals(FLASH)
				&& !type.equals(MP3))
			throw new FacebookException("Invalid media type");
		
		this.type = type;
	}
	
	/**
	 * Obtain the media source URL.
	 * 
	 * @return the URL.
	 */
	public String getSource() {
		return source;
	}
	
	/**
	 * Change the media source URL.
	 * 
	 * @param source the new URL.
	 */
	public void setSource(String source) {
		this.source = source;
	}
	
}

Concrete FlashMedia class.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

/**
 * FlashMedia
 * 
 * Represents a flash media for attachment.
 * 
 * @author Eki Baskoro
 *
 */
public class FlashMedia extends Media {

	private String imageSource = null;
	private int width = 0;
	private int height = 0;
	private int expandedWidth = 0;
	private int expandedHeight = 0;
	
	/**
	 * Create an instance of flash media.
	 * 
	 * @param source the flash source URL.
	 * @param imageSource the image source URL.
	 */
	public FlashMedia(String source, String imageSource) {
		this.type = FLASH;
		this.source = source;
		this.imageSource = imageSource;
	}
	
	/**
	 * Create an instance of flash media given the displayed dimension.
	 * 
	 * @param source the flash source URL.
	 * @param imageSource the image source URL.
	 * @param width the displayed width. Must be minimum of 30 pixels and maximum of 90 pixels.
	 * @param height the displayed height. Must be minimum of 30 pixels and maximum of 90 pixels.
	 * @throws FacebookException when the dimension restriction is not satisfied.
	 */
	public FlashMedia(String source, String imageSource, int width, int height) throws FacebookException {
		if (width < 30 || width > 90)
			throw new FacebookException("Width must be between 30 and 90 pixels");
		
		if (height < 30 || height > 90)
			throw new FacebookException("Height must be between 30 and 90 pixels");
		
		this.type = FLASH;
		this.source = source;
		this.imageSource = imageSource;
		this.width = width;
		this.height = height;
	}
	
	/**
	 * Create an instance of flash media given its displayed and expanded dimensions.
	 * 
	 * @param source the flash source URL.
	 * @param imageSource the image source URL.
	 * @param width the displayed width. Must be minimum of 30 pixels and maximum of 90 pixels.
	 * @param height the displayed height. Must be minimum of 30 pixels and maximum of 90 pixels.
	 * @param expandedWidth the expanded width. Must be minimum of 30 pixels and maximum of 460 pixels.
	 * @param expandedHeight the expanded height. Must be minimum of 30 pixels and maximum of 460 pixels.
	 * @throws FacebookException when the dimension restriction is not satisfied.
	 */
	public FlashMedia(String source, String imageSource, int width, int height, int expandedWidth, int expandedHeight) throws FacebookException {
		if (width < 30 || width > 90)
			throw new FacebookException("Width must be between 30 and 90 pixels");
		
		if (height < 30 || height > 90)
			throw new FacebookException("Height must be between 30 and 460 pixels");
		
		if (expandedWidth < 30 || expandedWidth > 460)
			throw new FacebookException("Expanded width must be between 30 and 460 pixels");
		
		if (expandedHeight < 30 || expandedHeight > 460)
			throw new FacebookException("Expanded height must be between 30 and 460 pixels");
		
		this.type = FLASH;
		this.source = source;
		this.imageSource = imageSource;
		this.width = width;
		this.height = height;
		this.expandedWidth = expandedWidth;
		this.expandedHeight = expandedHeight;
	}
	
	/**
	 * Obtain the image source URL.
	 * 
	 * @return the URL.
	 */
	public String getImageSource() {
		return imageSource;
	}
	
	/**
	 * Change the image source URL.
	 * 
	 * @param imageSource the new URL.
	 */
	public void setImageSource(String imageSource) {
		this.imageSource = imageSource;
	}
	
	/**
	 * Obtain the displayable width.
	 * 
	 * @return the width.
	 */
	public int getWidth() {
		return width;
	}
	
	/**
	 * Change the displayable width.
	 * 
	 * @param width the displayed width. Must be minimum of 30 pixels and maximum of 90 pixels.
	 * @throws FacebookException when the dimension restriction is not satisfied.
	 */
	public void setWidth(int width) throws FacebookException {
		if (width < 30 || width > 90)
			throw new FacebookException("Width must be between 30 and 90 pixels");
		
		this.width = width;
	}
	
	/**
	 * Obtain the displayable height.
	 * 
	 * @return the height.
	 */
	public int getHeight() {
		return height;
	}
	
	/**
	 * Change the displayable height.
	 * 
	 * @param height the displayed height. Must be minimum of 30 pixels and maximum of 90 pixels.
	 * @throws FacebookException when the dimension restriction is not satisfied.
	 */
	public void setHeight(int height) throws FacebookException {
		if (height < 30 || height > 90)
			throw new FacebookException("Height must be between 30 and 90 pixels");
		
		this.height = height;
	}
	
	/**
	 * Obtain the expanded width.
	 * 
	 * @return the width.
	 */
	public int getExpandedWidth() {
		return expandedWidth;
	}
	
	/**
	 * Change the expanded width.
	 * 
	 * @param expandedWidth the expanded width. Must be minimum of 30 pixels and maximum of 460 pixels.
	 * @throws FacebookException when the dimension restriction is not satisfied.
	 */
	public void setExpandedWidth(int expandedWidth) throws FacebookException {
		if (expandedWidth < 30 || expandedWidth > 90)
			throw new FacebookException("Expanded width must be between 30 and 460 pixels");
		
		this.expandedWidth = expandedWidth;
	}
	
	/**
	 * Obtain the expanded height.
	 * 
	 * @return the height.
	 */
	public int getExpandedHeight() {
		return expandedHeight;
	}
	
	/**
	 * Change the expanded height.
	 * 
	 * @param expandedHeight the expanded height. Must be minimum of 30 pixels and maximum of 460 pixels.
	 * @throws FacebookException when the dimension restriction is not satisfied.
	 */
	public void setExpandedHeight(int expandedHeight) throws FacebookException {
		if (expandedHeight < 30 || expandedHeight > 90)
			throw new FacebookException("Expanded height must be between 30 and 460 pixels");
		
		this.expandedHeight = expandedHeight;
	}
	
}

Concrete ImageMedia class.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

/**
 * ImageMedia
 * 
 * Represent an image media for attachment.
 * 
 * @author Eki Baskoro
 *
 */
public class ImageMedia extends Media {

	private String hyperlink = null;
	
	/**
	 * Create an instance of image media.
	 * 
	 * @param source the image URL.
	 * @param hyperlink the clickable URL.
	 */
	public ImageMedia(String source, String hyperlink) {
		this.type = IMAGE;
		this.source = source;
		this.hyperlink = hyperlink;
	}
	
	/**
	 * Obtain the clickable URL.
	 * 
	 * @return the URL.
	 */
	public String getHyperlink() {
		return hyperlink;
	}
	
	/**
	 * Change the clickable URL.
	 * 
	 * @param hyperlink the new URL.
	 */
	public void setHyperlink(String hyperlink) {
		this.hyperlink = hyperlink;
	}
	
}

Concrete Mp3Media class.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

/**
 * Mp3Media
 * 
 * Represents an MP3 media for attachment.
 * 
 * @author Eki Baskoro
 *
 */
public class Mp3Media extends Media {

	private String title;
	private String artist;
	private String album;
	
	/**
	 * Create an instance of MP3 media.
	 * 
	 * @param source the MP3 source URL.
	 * @param title the song's title.
	 * @param artist the song's artist.
	 * @param album the album.
	 */
	public Mp3Media(String source, String title, String artist, String album) {
		this.type = MP3;
		this.source = source;
		this.title = title;
		this.artist = artist;
		this.album = album;
	}
	
	/**
	 * Obtain the song's title.
	 * 
	 * @return the title.
	 */
	public String getTitle() {
		return title;
	}
	
	/**
	 * Change the song's title.
	 * 
	 * @param title the new title.
	 */
	public void setTitle(String title) {
		this.title = title;
	}
	
	/**
	 * Obtain the song's artist.
	 * 
	 * @return the artist.
	 */
	public String getArtist() {
		return artist;
	}
	
	/**
	 * Change the song's artist.
	 * 
	 * @param artist the new artist.
	 */
	public void setArtist(String artist) {
		this.artist = artist;
	}
	
	/**
	 * Obtain the album.
	 * 
	 * @return the album.
	 */
	public String getAlbum() {
		return album;
	}
	
	/**
	 * Change the album.
	 * 
	 * @param album the new album.
	 */
	public void setAlbum(String album) {
		this.album = album;
	}
	
}

Below is the Attachment class.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

import java.util.Vector;

/**
 * Attachment
 * 
 * Represents an attachment for stream publishing.
 * 
 * @author Eki Baskoro
 *
 */
public class Attachment {

	private String name;
	private String hyperlink;
	private String caption;
	private String description;
	private Vector medias = new Vector();
	
	/**
	 * Default constructor.
	 * 
	 * @param name the name of attachment.
	 * @param hyperlink the clickable URL.
	 * @param caption the caption of attachment.
	 * @param description the description.
	 */
	public Attachment(String name, String hyperlink, String caption, String description) {
		this.name = name;
		this.hyperlink = hyperlink;
		this.caption = caption;
		this.description = description;
	}
	
	/**
	 * Obtain the name.
	 * 
	 * @return the name.
	 */
	public String getName() {
		return name;
	}
	
	/**
	 * Change the name.
	 * 
	 * @param name the new name.
	 */
	public void setName(String name) {
		this.name = name;
	}
	
	/**
	 * Obtain the clickable URL.
	 * 
	 * @return the URL.
	 */
	public String getHyperlink() {
		return hyperlink;
	}
	
	/**
	 * Change the clickable URL.
	 * 
	 * @param hyperlink the new URL.
	 */
	public void setHyperlink(String hyperlink) {
		this.hyperlink = hyperlink;
	}
	
	/**
	 * Obtain the caption.
	 * 
	 * @return the caption.
	 */
	public String getCaption() {
		return caption;
	}
	
	/**
	 * Change the caption.
	 * 
	 * @param caption the new caption.
	 */
	public void setCaption(String caption) {
		this.caption = caption;
	}
	
	/**
	 * Obtain the description.
	 * 
	 * @return the description.
	 */
	public String getDescription() {
		return description;
	}
	
	/**
	 * Change the description.
	 * 
	 * @param description the new description.
	 */
	public void setDescription(String description) {
		this.description = description;
	}
	/**
	 * Obtain the collection of media.
	 * 
	 * @return an array of media instances.
	 */
	public Media[] getMedias() {
		if (medias.size() == 0)
			return null;
		
		Media[] result = new Media[medias.size()];
		medias.copyInto(result);
		
		return result;
	}
	
	/**
	 * Add a media to the attachment.
	 * 
	 * @param media the media instance to add.
	 * @throws FacebookException when an error occurs.
	 */
	public void addMedia(Media media) throws FacebookException {
		if (media == null)
			return;
		
		if (medias.size() > 0 && medias.lastElement().getClass().equals(media.getClass()))
			throw new FacebookException("Media type differs from existing");
		
		if (media instanceof ImageMedia) {
			if (medias.size() == 5)
				throw new FacebookException("Can only include a maximum of five images");
		}
		
		medias.addElement(media);
	}
	
	/**
	 * Remove a media instance from the attachment.
	 * 
	 * @param media the media instance to remove.
	 */
	public void removeMedia(Media media) {
		if (media == null)
			return;
		
		medias.removeElement(media);
	}
	
	/**
	 * Remove a media given its index to the collection.
	 * 
	 * @param index the zero-based index.
	 * @throws FacebookException when an error occurs.
	 */
	public void removeMedia(int index) throws FacebookException {
		try {
			medias.removeElementAt(index);
		} catch (ArrayIndexOutOfBoundsException e) {
			throw new FacebookException(e.getMessage());
		}
	}
	
}

Next is the ActionLink class.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

/**
 * ActionLink
 * 
 * Represents an action link.
 * 
 * @author Eki Baskoro
 *
 */
public class ActionLink {

	private String text;
	private String hyperlink;
	
	/**
	 * Default constructor.
	 * 
	 * @param text the text to be displayed.
	 * @param hyperlink the URL.
	 */
	public ActionLink(String text, String hyperlink) {
		this.text = text;
		this.hyperlink = hyperlink;
	}
	
	/**
	 * Obtain the text to be displayed.
	 * 
	 * @return the text.
	 */
	public String getText() {
		return text;
	}
	
	/**
	 * Change the text to be displayed.
	 * 
	 * @param text the new text.
	 */
	public void setText(String text) {
		this.text = text;
	}
	
	/**
	 * Obtain the clickable URL.
	 * 
	 * @return the URL.
	 */
	public String getHyperlink() {
		return hyperlink;
	}
	
	/**
	 * Change the clickable URL.
	 * 
	 * @param hyperlink the new URL.
	 */
	public void setHyperlink(String hyperlink) {
		this.hyperlink = hyperlink;
	}
	
}

Righteo, now, how do we use the above? The following is a sample application.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook.samples;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;

import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Display;
import net.rim.device.api.system.PersistentObject;
import net.rim.device.api.system.PersistentStore;
import net.rim.device.api.ui.DrawStyle;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.Font;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.component.EditField;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.ListField;
import net.rim.device.api.ui.component.ListFieldCallback;
import net.rim.device.api.ui.component.ObjectChoiceField;
import net.rim.device.api.ui.component.SeparatorField;
import net.rim.device.api.ui.container.HorizontalFieldManager;

import blackberry.action.ActionListener;
import blackberry.facebook.ActionLink;
import blackberry.facebook.Attachment;
import blackberry.facebook.CustomPrivacy;
import blackberry.facebook.FacebookException;
import blackberry.facebook.FacebookFacade;
import blackberry.facebook.FlashMedia;
import blackberry.facebook.Post;
import blackberry.facebook.Privacy;
import blackberry.facebook.Stream;
import blackberry.facebook.User;
import blackberry.facebook.ui.LoginScreen;
import blackberry.ui.AbstractScreen;

/**
 * TestBB
 * 
 * The main UiApplication.
 * 
 * @author Eki Baskoro
 * @version 0.1
 *
 */
public class TestBB extends UiApplication implements ActionListener {

	private static final String APPKEY = ""; // Your Application Key
	private static final String SECRET = ""; // Your Application Secret
	
	private static PersistentObject store;
	private static FacebookFacade facebookFacade;
	//private static FacebookChatFacade facebookChatFacade; // Uncomment if want to test Facebook Chat
	
	private LoginScreen loginScreen;
	//private AuthorizeScreen authorizeScreen; // Uncomment if want to test Facebook Chat
	private HomeScreen homeScreen;
	private UpdateStatusScreen updateStatusScreen;
	private StatusUpdatesScreen statusUpdatesScreen;
	private UploadPhotoScreen uploadPhotoScreen;
	private FriendsListScreen friendsListScreen;
	private PostWallScreen postWallScreen;
	//private ChatScreen chatScreen; // uncomment if want to test Facebook Chat
	
	static {
		store = PersistentStore.getPersistentObject(0x200eab09890L);
		
		synchronized (store) {
			if (store.getContents() == null) {
				store.setContents(new FacebookFacade(APPKEY, SECRET));
				store.commit();
			}
		}
		
		facebookFacade = (FacebookFacade)store.getContents();
		//facebookChatFacade = new FacebookChatFacade(facebookFacade); // Uncomment if want to test Facebook Chat
	}
	
	/**
	 * Default constructor.
	 * 
	 */
	public TestBB() {
		if (!facebookFacade.hasSession()) {
			facebookFacade = new FacebookFacade(APPKEY, SECRET);
			
			loginScreen = new LoginScreen(facebookFacade);
			loginScreen.setActionListener(this);
			
			pushScreen(loginScreen);
		} else {
			homeScreen = new HomeScreen(facebookFacade);
			homeScreen.setActionListener(this);
			
			updateStatusScreen = new UpdateStatusScreen(facebookFacade);
			updateStatusScreen.setActionListener(this);
			
			pushScreen(homeScreen);
			
			try {
				long uid = facebookFacade.getLoggedInUserId();
				final User user = facebookFacade.getUser(uid);
				getApplication().invokeLater(new Runnable() {
					
					public void run() {
						Dialog.inform("Hello " + user.getFirstName() + "!");
					}
				});
			} catch (FacebookException e) {
				Dialog.alert(e.getMessage());
			}
		}
	}
	
	public void execute(String action, AbstractScreen screen) {
		if (screen == loginScreen) {
			if (action == "success") {
				try {
					popScreen(loginScreen);
				} catch (IllegalArgumentException e) {}
				
				try {
					synchronized (store) {
						store.setContents(facebookFacade);
						store.commit();
					}
					
					if (homeScreen == null) {
						homeScreen = new HomeScreen(facebookFacade);
						homeScreen.setActionListener(this);
					}
					
					pushScreen(homeScreen);
					
					long uid = facebookFacade.getLoggedInUserId();
					User user = facebookFacade.getUser(uid);
					Dialog.inform("Hello " + user.getFirstName() + "!");
				} catch (Exception e) {
					Dialog.alert(e.getMessage());
				}
			} else if (action == "error") {
				Dialog.alert("Error encountered");
			}
		}
		/* Uncomment if want to test Facebook Chat
		else if (screen == authorizeScreen) {
			if (action == "success") {
				try {
					popScreen(authorizeScreen);
				} catch (IllegalArgumentException e) {}
				
				
				if (chatScreen == null) {
					chatScreen = new ChatScreen(facebookChatFacade);
					chatScreen.setActionListener(this);
					facebookChatFacade.addListener(chatScreen);
				}
				
				pushScreen(chatScreen);
				facebookChatFacade.connect();
			} else if (action == "error") {
				Dialog.alert("Error encountered");
			}
		}*/
		else if (screen == homeScreen) {
			if (action == "updateStatus") {
				if (updateStatusScreen == null) {
					updateStatusScreen = new UpdateStatusScreen(facebookFacade);
					updateStatusScreen.setActionListener(this);
				}
				
				pushScreen(updateStatusScreen);
			} else if (action == "statusUpdates") {
				if (statusUpdatesScreen == null) {
					statusUpdatesScreen = new StatusUpdatesScreen(facebookFacade);
				}
				
				statusUpdatesScreen.loadList();
				pushScreen(statusUpdatesScreen);
			} else if (action == "uploadPhotos") {
				if (uploadPhotoScreen == null) {
					uploadPhotoScreen = new UploadPhotoScreen();
				}
				
				pushScreen(uploadPhotoScreen);
			} else if (action == "friendsList") {
				if (friendsListScreen == null) {
					friendsListScreen = new FriendsListScreen(facebookFacade);
				}
				
				friendsListScreen.loadList();
				pushScreen(friendsListScreen);
			} else if (action == "postWall") {
				if (postWallScreen == null) {
					postWallScreen = new PostWallScreen(facebookFacade);
					postWallScreen.setActionListener(this);
				}
				
				postWallScreen.loadList();
				pushScreen(postWallScreen);
			} else if (action == "chat") {
				/* Uncomment if want to test Facebook Chat
				if (chatScreen == null) {
					chatScreen = new ChatScreen(facebookChatFacade);
					chatScreen.setActionListener(this);
					facebookChatFacade.addListener(chatScreen);
				}
				
				try {
					if (facebookFacade.hasAppPermission(FacebookFacade.EXT_PERM_XMPP_LOGIN)) {
						pushScreen(chatScreen);
						facebookChatFacade.connect();
					} else {
						if (authorizeScreen == null) {
							authorizeScreen = new AuthorizeScreen(facebookFacade);
							authorizeScreen.setActionListener(this);
						}
						
						pushScreen(authorizeScreen);
					}
				} catch (FacebookException e) {
					Dialog.alert(e.getMessage());
				}
				*/
			}
		} else if (screen == updateStatusScreen) {
			if (action == "statusUpdated") {
				Dialog.inform("Status updated");
				
				try {
					popScreen(updateStatusScreen);
				} catch (IllegalArgumentException e) {}
			} else if (action == "error") {
				Dialog.alert("Error encountered");
			}
		} else if (screen == postWallScreen) {
			if (action == "posted") {
				Dialog.inform("Wall posted");
				
				try {
					popScreen(postWallScreen);
				} catch (IllegalArgumentException e) {}
			} else if (action == "error") {
				Dialog.alert("Error encountered");
			}
		}
		/* uncomment if want to test Facebook Chat
		else if (screen == chatScreen) {
			if (action == "done") {
				try {
					popScreen(chatScreen);
				} catch (IllegalArgumentException e) {}
			}
		}*/
	}
	
	public static void main(String args[]) {
		TestBB app = new TestBB();
		app.enterEventDispatcher();
	}
}

final class HomeScreen extends AbstractScreen {
	
	private ButtonField updateStatusButton;
	private ButtonField statusUpdateButton;
	private ButtonField uploadPhotoButton;
	private ButtonField friendListButton;
	private ButtonField inviteFriendButton;
	private ButtonField pokeButton;
	private ButtonField wallButton;
	private ButtonField sendMessageButton;
	private ButtonField chatButton;
	
	public HomeScreen(FacebookFacade facebookFacade) {
		setTitle(new LabelField("TestBB Application", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH));
		
		HorizontalFieldManager topManager = new HorizontalFieldManager(Manager.HORIZONTAL_SCROLL);
		add(topManager);
		
		updateStatusButton = new ButtonField("Update Status");
		updateStatusButton.setChangeListener(new FieldChangeListener() {
			
			public void fieldChanged(Field field, int context) {
				notifyActionListener("updateStatus");
			}
			
		});
		topManager.add(updateStatusButton);
		
		statusUpdateButton = new ButtonField("Status Updates");
		statusUpdateButton.setChangeListener(new FieldChangeListener() {
			
			public void fieldChanged(Field field, int context) {
				notifyActionListener("statusUpdates");
			}
			
		});
		topManager.add(statusUpdateButton);
		
		uploadPhotoButton = new ButtonField("Upload Photo");
		uploadPhotoButton.setChangeListener(new FieldChangeListener() {
			
			public void fieldChanged(Field field, int context) {
				notifyActionListener("uploadPhotos");
			}
			
		});
		topManager.add(uploadPhotoButton);
		
		friendListButton = new ButtonField("Friends List");
		friendListButton.setChangeListener(new FieldChangeListener() {
			
			public void fieldChanged(Field field, int context) {
				notifyActionListener("friendsList");
			}
			
		});
		topManager.add(friendListButton);
		
		inviteFriendButton = new ButtonField("Invite a Friend");
		topManager.add(inviteFriendButton);
		
		pokeButton = new ButtonField("Poke a Friend");
		topManager.add(pokeButton);
		
		wallButton = new ButtonField("Write on a Wall");
		wallButton.setChangeListener(new FieldChangeListener() {
			
			public void fieldChanged(Field field, int context) {
				notifyActionListener("postWall");
			}
			
		});
		topManager.add(wallButton);
		
		sendMessageButton = new ButtonField("Send a Message");
		topManager.add(sendMessageButton);
		
		chatButton = new ButtonField("Chat");
		chatButton.setChangeListener(new FieldChangeListener() {
			
			public void fieldChanged(Field field, int context) {
				notifyActionListener("chat");
			}
			
		});
		topManager.add(chatButton);
		
		add(new SeparatorField());
	}
	
	public boolean onClose() {
		if (Dialog.ask(Dialog.D_YES_NO, "Exit now?", Dialog.NO) == Dialog.YES) {
			System.exit(0);
			
			return true;
		}
		
		return false;
	}
}

final class UpdateStatusScreen extends AbstractScreen {
	
	private EditField editField;
	private ButtonField buttonField;
	
	UpdateStatusScreen(final FacebookFacade facebookFacade) {
		setTitle(new LabelField("Update Status", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH));
		
		editField = new EditField(LabelField.USE_ALL_WIDTH);
		add(editField);
		
		buttonField = new ButtonField("Update My Status");
		buttonField.setChangeListener(new FieldChangeListener() {
			
			public void fieldChanged(Field field, int context) {
				try {
					facebookFacade.updateStatus(editField.getText());
					notifyActionListener("statusUpdated");
				} catch (Exception e) {
					notifyActionListener("error");
				}
			}
			
		});
		add(buttonField);
	}
}

final class PostWallScreen extends AbstractScreen {
	
	private FacebookFacade facebookFacade = null;
	private User[] users = null;
	private ObjectChoiceField objectChoiceField;
	private EditField titleEditField;
	private EditField hrefEditField;
	private EditField captionEditField;
	private EditField descriptionEditField;
	private ButtonField buttonField;
	
	PostWallScreen(final FacebookFacade facebookFacade) {
		this.facebookFacade = facebookFacade;
		
		setTitle(new LabelField("Update Status", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH));
		
		objectChoiceField = new ObjectChoiceField();
		objectChoiceField.setLabel("Post Wall to");
		add(objectChoiceField);
		
		add(new SeparatorField(SeparatorField.LINE_HORIZONTAL));
		
		titleEditField = new EditField("Title:", "", 80, LabelField.USE_ALL_WIDTH);
		add(titleEditField);
		
		hrefEditField = new EditField("Link:", "", 80, LabelField.USE_ALL_WIDTH);
		add(hrefEditField);
		
		captionEditField = new EditField("Caption:", "", 80, LabelField.USE_ALL_WIDTH);
		add(captionEditField);
		
		descriptionEditField = new EditField("Content:", "", 255, LabelField.USE_ALL_WIDTH);
		add(descriptionEditField);
		
		buttonField = new ButtonField("Post");
		buttonField.setChangeListener(new FieldChangeListener() {
			
			public void fieldChanged(Field field, int context) {
				if (users == null)
					return;
				
				try {
					Privacy privacy = new CustomPrivacy(CustomPrivacy.NO_FRIENDS);
					Attachment attachment = new Attachment(titleEditField.getText(), hrefEditField.getText(), captionEditField.getText(), descriptionEditField.getText());
					FlashMedia flashMedia = new FlashMedia("http://www.mapsofwar.com/photos/EMPIRE17.swf", "http://icanhascheezburger.files.wordpress.com/2009/04/funny-pictures-hairless-cat-phones-home.jpg", 90, 90);
					attachment.addMedia(flashMedia);
					ActionLink[] actionLinks = new ActionLink[1];
					actionLinks[0] = new ActionLink(titleEditField.getText(), hrefEditField.getText());
					facebookFacade.publishStream(null, String.valueOf(users[objectChoiceField.getSelectedIndex()].getUid()), privacy, descriptionEditField.getText(), attachment, actionLinks);
					notifyActionListener("posted");
				} catch (FacebookException e) {
					notifyActionListener("error");
				}
			}
			
		});
		add(buttonField);
	}
	
	public void loadList() {
		try {
			User[] friends = facebookFacade.getFriends();
			
			if (friends == null) {
				users = new User[1];
			} else {
				users = new User[friends.length+1];
			}
			
			long uid = facebookFacade.getLoggedInUserId();
			users[0] = facebookFacade.getUser(uid);
			
			for (int i = 1; i < (friends.length+1); i ++) {
				users[i] = friends[i-1];
			}
			
			objectChoiceField.setChoices(users);
		} catch (FacebookException e) {
			Dialog.alert("Error: " + e.getMessage());
		}
	} 
}

final class StatusUpdatesScreen extends AbstractScreen {
	
	private FacebookFacade facebookFacade = null;
	private ListField listField;
	private StreamListCallback streamListCallback = new StreamListCallback();
	
	public StatusUpdatesScreen(FacebookFacade facebookFacade) {
		this.facebookFacade = facebookFacade;
		
		setTitle(new LabelField("Status Updates", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH));
		
		listField = new ListField();
		listField.setRowHeight(50);
		listField.setCallback(streamListCallback);
		add(listField);
	}
	
	public void loadList() {
		while (listField.getSize() > 0) {
			listField.delete(0);
		}
		
		try {
			Stream stream = facebookFacade.getStream(10);
			streamListCallback.clear();
			
			for (int i = 0; i < stream.getPosts().length; i ++) {
				listField.insert(listField.getSize());
				streamListCallback.add(stream.getPosts()[i]);
			}
		} catch (FacebookException e) {
			Dialog.alert("Error: " + e.getMessage());
		}
	}
	
	private class StreamListCallback implements ListFieldCallback {
		
		private Vector posts = new Vector();
		
		public StreamListCallback() {
		}
		
		public void clear() {
			posts.removeAllElements();
		}
		
		public void add(Post post) {
			posts.addElement(post);
		}
		
		public void insert(Post post, int index) {
			posts.insertElementAt(post, index);
		}
		
		public void drawListRow(ListField listField, Graphics g, int index, int y, int width) {
			if (index < posts.size()) {
				int height = listField.getRowHeight();
				Post post = (Post)posts.elementAt(index);
				
				g.drawText(post.getMessage(), 52, y, 0, width-52);
				//g.drawText(friend.getStatus(), 52, y+(height/2), DrawStyle.ELLIPSIS, width-52);
				g.drawText(post.getCommentsCount() + " comments", 52, y+(height/2), DrawStyle.ELLIPSIS, width-52);
				g.drawLine(0, y+height-1, width, y+height-1);
			}
		}

		public Object get(ListField listField, int index) {
			if (index < posts.size()) {
				return posts.elementAt(index);
			}
			
			return null;
		}

		public int getPreferredWidth(ListField listField) {
			return Display.getWidth();
		}

		public int indexOfList(ListField listField, String prefix, int start) {
			for (int i = start; i < posts.size(); i ++) {
				Post post = (Post)posts.elementAt(i);
				
				if (post.getMessage().indexOf(prefix) > -1)
					return i;
			}
			
			return -1;
		}
	}
}

final class UploadPhotoScreen extends AbstractScreen {
	
	public UploadPhotoScreen() {
		setTitle(new LabelField("Upload Photo", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH));
	}
}

final class FriendsListScreen extends AbstractScreen {
	
	private FacebookFacade facebookFacade = null;
	private ListField listField;
	private FriendsListFieldCallback friendsListFieldCallback = new FriendsListFieldCallback();
	
	public FriendsListScreen(FacebookFacade facebookFacade) {
		this.facebookFacade = facebookFacade;
		
		setTitle(new LabelField("Friends List", LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH));
		
		listField = new ListField();
		listField.setRowHeight(50);
		listField.setCallback(friendsListFieldCallback);
		add(listField);
	}
	
	public void loadList() {
		while (listField.getSize() > 0) {
			listField.delete(0);
		}
		
		try {
			User[] friends = facebookFacade.getFriends();
			friendsListFieldCallback.clear();
			
			for (int i = 0; i < friends.length; i ++) {
				listField.insert(listField.getSize());
				friendsListFieldCallback.add(friends[i]);
			}
			
			friendsListFieldCallback.loadBitmaps();
		} catch (FacebookException e) {
			Dialog.alert("Error: " + e.getMessage());
		}
	}
	
	private class FriendsListFieldCallback implements ListFieldCallback {

		private Vector friends = new Vector();
		private Hashtable pictureBitmaps = new Hashtable();
		
		public FriendsListFieldCallback() {
		}
		
		public void clear() {
			friends.removeAllElements();
			pictureBitmaps.clear();
		}
		
		public void add(User friend) {
			friends.addElement(friend);
		}
		
		public void insert(User friend, int index) {
			friends.insertElementAt(friend, index);
		}
		
		public void loadBitmaps() {
			(new BitmapThread()).start();
		}
		
		public void drawListRow(ListField listField, Graphics g, int index, int y, int width) {
			if (index < friends.size()) {
				int height = listField.getRowHeight();
				User friend = (User)friends.elementAt(index);
				Bitmap bitmap = getBitmap(friend.getUid());
				
				if (bitmap != null) {
					g.drawBitmap(0, y+((height-Math.min(bitmap.getHeight(), height))/2), 50, height, bitmap, 0, 0);
				}
				
				Font font = Font.getDefault();
				font.derive(Font.BOLD);
				g.setFont(font);
				g.drawText(friend.getName(), 52, y, 0, width-52);
				g.drawText(friend.getStatus(), 52, y+(height/2), DrawStyle.ELLIPSIS, width-52);
				g.drawLine(0, y+height-1, width, y+height-1);
			}
		}

		public Object get(ListField listField, int index) {
			if (index < friends.size()) {
				return friends.elementAt(index);
			}
			
			return null;
		}

		public int getPreferredWidth(ListField listField) {
			return Display.getWidth();
		}

		public int indexOfList(ListField listField, String prefix, int start) {
			for (int i = start; i < friends.size(); i ++) {
				User friend = (User)friends.elementAt(i);
				
				if (friend.getName().indexOf(prefix) > -1)
					return i;
			}
			
			return -1;
		}
		
		private Bitmap getBitmap(long uid) {
			return (Bitmap)pictureBitmaps.get(new Long(uid));
		}
		
		private class BitmapThread extends Thread {
			
			public void run() {
				Enumeration friendsEnum = friends.elements();
				
				while (friendsEnum.hasMoreElements()) {
					User friend = (User)friendsEnum.nextElement();
					long uid = friend.getUid();
					String url = friend.getPictureSmallUrl();
					
					if (url != null && url.length() > 0) {
						HttpConnection connection = null;
						byte[] data = new byte[500*1024];
						
						try {
							connection = (HttpConnection)Connector.open(url);
							
							InputStream is = connection.openInputStream();
							int length = is.read(data, 0, 500*1024);
							is.close();
							
							Bitmap bitmap = Bitmap.createBitmapFromBytes(data, 0, length, 1);
							pictureBitmaps.put(new Long(uid), bitmap);
							listField.invalidate();
						} catch (IOException e) {
							Dialog.alert(e.getMessage());
						} finally {
							if (connection != null) {
								try { connection.close(); } catch (IOException e) {}
							}
						}
					}
				}
			}
		}
	}
}

/* uncomment if want to test chat 
final class ChatScreen extends AbstractScreen implements XmppListener {
	
	private TextField textField;
	private EditField editField;
	private ButtonField buttonField;
	private FacebookChatFacade facebookChatFacade = null;
	private String resource = "";
	
	ChatScreen(final FacebookChatFacade facebookChatFacade) {
		this.facebookChatFacade = facebookChatFacade;

		VerticalFieldManager vfm = new VerticalFieldManager();
		add(vfm);
		
		textField = new TextField(TextField.READONLY);
		textField.setText("Logging in...");
		vfm.add(textField);
		
		FlowFieldManager ffm = new FlowFieldManager();
		vfm.add(ffm);
		
		editField = new EditField(EditField.FIELD_LEFT);
		ffm.add(editField);
		
		buttonField = new ButtonField("Send");
		buttonField.setChangeListener(new FieldChangeListener() {
			
			public void fieldChanged(Field field, int context) {
				
				String jid = ""; // receiver, eg. "eki.baskoro@chat.facebook.com"
				facebookChatFacade.sendMessage(jid + '/' + resource, editField.getText());
				addText("me: " + editField.getText());
				editField.setText("");
				
			}
		});
		ffm.add(buttonField);
	}
	
	private void addText(String line) {
		StringBuffer buffer = new StringBuffer(textField.getText());
		
		if (textField.getText().length() > 0) {
			buffer.append('\n');
		}
		
		buffer.append(line);
		textField.setText(buffer.toString());
	}
	
	public void onAuth(String resource) {
		this.resource = resource;
		addText("Logged in.");
		//facebookChatFacade.setStatus("dnd", "Happy", 5);
	}

	public void onAuthFailed(String message) {
		addText("Login failed.");
	}

	public void onConnFailed(String message) {
		Dialog.alert("Facebook Chat Connection Error:\n" + message);
	}

	public void onContactEvent(String jid, String name, String group, String subscription) {
		addText("jid=" + jid + " name=" + name + " group=" + group + " subs=" + subscription);
	}

	public void onContactOverEvent() {
		addText("Contact over event.");
	}

	public void onMessageEvent(String from, String body) {
		addText(from + ": " + body);
	}

	public void onStatusEvent(String jid, String show, String status) {
		addText("jid=" + jid + " show=" + show + " status=" + status);
	}

	public void onSubscribeEvent(String jid) {
		addText("jid=" + jid);
	}

	public void onUnsubscribeEvent(String jid) {
		addText("jid=" + jid);
	}
	
}
*/

Facebook Chat Integration

Currently I am working on the Facebook Chat Facade that encapsulates Facebook's newest XMPP Chat service. If you would like to test you can comment out the Facebook Chat related lines above in addition to the classes below. Currently, the X-FACEBOOK-PLATFORM SASL authentication method works however sending messages and setting presence are still not working.

I would like to thank to the JXA project which I have leveraged the work from.

The screen prompting for xmpp_login extended permission.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook.ui;

import java.io.IOException;
import java.io.OutputStream;

import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;

import net.rim.device.api.browser.field.BrowserContent;
import net.rim.device.api.browser.field.BrowserContentChangedEvent;
import net.rim.device.api.browser.field.Event;
import net.rim.device.api.browser.field.RedirectEvent;
import net.rim.device.api.browser.field.RenderingApplication;
import net.rim.device.api.browser.field.RenderingException;
import net.rim.device.api.browser.field.RenderingOptions;
import net.rim.device.api.browser.field.RenderingSession;
import net.rim.device.api.browser.field.RequestedResource;
import net.rim.device.api.browser.field.UrlRequestedEvent;
import net.rim.device.api.io.http.HttpProtocolConstants;
import net.rim.device.api.system.Application;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.Field;

import blackberry.facebook.FacebookFacade;
import blackberry.ui.AbstractScreen;

/**
 * AuthorizeScreen
 * 
 * Acts as an embedded browser to display the extended permission authorization
 * screen.
 * In the future, cookie can be supported hence once user needs not to login for
 * the second time.
 * 
 * @author Eki Baskoro
 *
 */
public class AuthorizeScreen extends AbstractScreen {

	private RenderingApplicationImpl renderer = new RenderingApplicationImpl();
	
	/**
	 * Create an instance of Authorize screen.
	 * 
	 * @param facebookFacade the injected Facebook facade instance.
	 */
	public AuthorizeScreen(FacebookFacade facebookFacade) {
		StringBuffer url = new StringBuffer()
			.append("http://m.facebook.com/authorize.php?")
			.append("api_key=").append(facebookFacade.getApplicationKey())
			.append("&ext_perm=xmpp_login")
			.append("&next=http://www.facebook.com/connect/login_success.html");
		
		(new FetchThread(url.toString())).start();
	}
	
	private void display(HttpConnection connection) {
		BrowserContent browserContent = renderer.getBrowserContent(connection);
		
		if (browserContent != null) {
			Field field = browserContent.getDisplayableContent();
			
			if (field != null) {
				synchronized (Application.getEventLock()) {
					deleteAll();
					add(field);
				}
			}
			
			try { browserContent.finishLoading(); }
			catch (RenderingException e) {}
		}
	}
	
	private class RenderingApplicationImpl implements RenderingApplication {
		
		private RenderingSession renderingSession = RenderingSession.getNewInstance();
		
		public RenderingApplicationImpl() {
			renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.JAVASCRIPT_ENABLED, true);
			renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.JAVASCRIPT_LOCATION_ENABLED, true);
			renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.WAP_MODE, true);
			renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.ENABLE_WML, true);
			renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.ENABLE_EMBEDDED_RICH_CONTENT, true);
			renderingSession.getRenderingOptions().setProperty(RenderingOptions.CORE_OPTIONS_GUID, RenderingOptions.ENABLE_CSS, true);
		}
		
		public BrowserContent getBrowserContent(HttpConnection connection) {
			try {
				return renderingSession.getBrowserContent(connection, this, null);
			} catch (RenderingException e) {
				return null;
			}
		}
		
		/**
	     * @see net.rim.device.api.browser.RenderingApplication#eventOccurred(net.rim.device.api.browser.Event)
	     */
	    public Object eventOccurred(Event event) {
	        int eventId = event.getUID();

	        switch (eventId) {

	            case Event.EVENT_URL_REQUESTED: {
	                UrlRequestedEvent urlRequestedEvent = (UrlRequestedEvent)event;
	                byte[] postData = urlRequestedEvent.getPostData();
	                
	                if (postData != null) {
	                	(new FetchThread(urlRequestedEvent.getURL(), urlRequestedEvent.getPostData())).start();
	                } else {
	                	(new FetchThread(urlRequestedEvent.getURL())).start();
	                }
	                
	                break;
	            }
	            
	            case Event.EVENT_BROWSER_CONTENT_CHANGED: {
	                BrowserContentChangedEvent browserContentChangedEvent = (BrowserContentChangedEvent)event;
	           
	                if (browserContentChangedEvent.getSource() instanceof BrowserContent) {
	                    BrowserContent browserContent = (BrowserContent)browserContentChangedEvent.getSource();
	                    final String newUrl = browserContent.getURL();
	                    
	                    if (newUrl.startsWith("http://www.facebook.com/connect/login_success.html")) {
	                    	Application.getApplication().invokeLater(new Runnable() {
	                    		
	                    		public void run() {
	                    			notifyActionListener("success");
	                    		}
	                    	});
	                    }
	                }

	                break;
	            }
	            
	            case Event.EVENT_REDIRECT: {
	            	RedirectEvent redirectEvent = (RedirectEvent)event;
	            	(new FetchThread(redirectEvent.getLocation())).start();
	            	break;
	            }
	            
	            case Event.EVENT_CLOSE:              // Close the appication
	                break;
	           
	            case Event.EVENT_SET_HEADER :        // no cache support
	            	break;
	            	
	            case Event.EVENT_SET_HTTP_COOKIE: {  // no cookie support
	            	break;
	            }
	            
	            case Event.EVENT_HISTORY :           // no history support           
	            case Event.EVENT_EXECUTING_SCRIPT :  // no progress bar is supported
	            case Event.EVENT_FULL_WINDOW :       // no full window support
	            case Event.EVENT_STOP :              // no stop loading support
	            default :
	            	break;
	        }

	        return null;
	    }

	    /**
	     * @see net.rim.device.api.browser.RenderingApplication#getAvailableHeight(net.rim.device.api.browser.BrowserContent)

	     */
	    public int getAvailableHeight(BrowserContent browserField) {
	        return Display.getHeight();
	    }

	    /**
	     * @see net.rim.device.api.browser.RenderingApplication#getAvailableWidth(net.rim.device.api.browser.BrowserContent)

	     */
	    public int getAvailableWidth(BrowserContent browserField) {
	        return Display.getWidth();
	    }

	    /**
	     * @see net.rim.device.api.browser.RenderingApplication#getHistoryPosition(net.rim.device.api.browser.BrowserContent)

	     */
	    public int getHistoryPosition(BrowserContent browserField) {
	        return 0; // no history support
	    }

	    /**
	     * @see net.rim.device.api.browser.RenderingApplication#getHTTPCookie(java.lang.String)
	     */
	    public String getHTTPCookie(String url) {
	        return null; // no cookie support
	    }

	    /**
	     * @see net.rim.device.api.browser.RenderingApplication#getResource(net.rim.device.api.browser.RequestedResource,
	     *      net.rim.device.api.browser.BrowserContent)
	     */
	    public HttpConnection getResource(RequestedResource resource, BrowserContent referrer) {

	        if (resource == null) {
	            return null;
	        }

	        if (resource.isCacheOnly()) {
	            return null; // no cache support
	        }

	        final String url = resource.getUrl();

	        if (url == null) {
	            return null;
	        }

	        if (referrer == null) {
	        	try { return (HttpConnection)Connector.open(url); }
	        	catch (IOException e) {}
	        } else {
	        	(new ResourceFetchThread(resource, referrer)).start();
	        }

	        return null;
	    }

	    /**
	     * @see net.rim.device.api.browser.RenderingApplication#invokeRunnable(java.lang.Runnable)
	     */
	    public void invokeRunnable(Runnable runnable) {
	        (new Thread(runnable)).run();
	    }
	}
	
	/**
	 * Do GET or POST
	 * 
	 */
	private class FetchThread extends Thread {
		
		private String absoluteUrl = null;
		private String method = HttpConnection.GET;
		private byte[] data = null;
		
		public FetchThread(String absoluteUrl) {
			this.absoluteUrl = absoluteUrl;
		}
		
		public FetchThread(String absoluteUrl, String method) {
			this.absoluteUrl = absoluteUrl;
			this.method = method;
		}
		
		public FetchThread(String absoluteUrl, byte[] data) {
			this.absoluteUrl = absoluteUrl;
			this.method = HttpConnection.POST;
			this.data = data;
		}
		
		public void run() {
			HttpConnection connection = null;
			
			try {
				connection = (HttpConnection)Connector.open(absoluteUrl);
				connection.setRequestProperty("x-rim-gw-properties", "16.10");
				connection.setRequestProperty("x-rim-transcode-content", "*/*");
				connection.setRequestProperty("x-rim-accept-encoding", "yk;v=3;m=384");
				connection.setRequestProperty("x-wap-profile", "\"http://www.blackberry.net/go/mobile/profiles/uaprof/8320/4.5.0.rdf\"");
				connection.setRequestProperty("profile", "http://www.blackberry.net/go/mobile/profiles/uaprof/8320/4.5.0.rdf");
				connection.setRequestProperty("User-Agent", "BlackBerry8320/4.5.0.44 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/-1");
				connection.setRequestProperty("Accept", "application/vnd.rim.html,text/html,application/xhtml+xml,application/vnd.wap.xhtml+xml,application/vnd.wap.wmlc;q=0.9,application/vnd.wap.wmlscriptc;q=0.7,text/vnd.wap.wml;q=0.7,text/vnd.sun.j2me.app-descriptor,image/vnd.rim.png,image/jpeg,application/x-vnd.rim.pme.b,application/vnd.rim.ucs,image/gif;anim=1,application/vnd.rim.css;v=1,text/css;media=screen,*/*;q=0.5");
				connection.setRequestProperty("x-rim-original-accept", "application/vnd.rim.html,text/html,application/xhtml+xml,application/vnd.wap.xhtml+xml,application/vnd.wap.wmlc;q=0.9,application/vnd.wap.wmlscriptc;q=0.7,text/vnd.wap.wml;q=0.7,text/vnd.sun.j2me.app-descriptor,image/vnd.rim.png,image/jpeg,application/x-vnd.rim.pme.b,application/vnd.rim.ucs,image/gif;anim=1,application/vnd.rim.css;v=1,text/css;media=screen,*/*;q=0.");
				
				if (method == HttpConnection.POST) {
					connection.setRequestMethod(HttpConnection.POST);
					connection.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_TYPE, HttpProtocolConstants.CONTENT_TYPE_APPLICATION_X_WWW_FORM_URLENCODED);
					connection.setRequestProperty(HttpProtocolConstants.HEADER_CONTENT_LENGTH, String.valueOf(data.length));
					OutputStream os = connection.openOutputStream();
					os.write(data);
				} else if (method == HttpConnection.GET) {
					connection.setRequestMethod(HttpConnection.GET);
				}
				
				display(connection);
			} catch (IOException e) {
			} finally {
				if (connection != null) {
					try { connection.close(); }
					catch (IOException e) {}
				}
			}
		}
	}
	
	/**
	 * Fetch requested resources.
	 * 
	 * @author Eki Baskoro
	 *
	 */
	private class ResourceFetchThread extends Thread {
		
		private RequestedResource requestedResource = null;
		private BrowserContent browserContent = null;
		
		public ResourceFetchThread(RequestedResource requestedResource, BrowserContent browserContent) {
			this.requestedResource = requestedResource;
			this.browserContent = browserContent;
		}
		
		public void run() {
			HttpConnection connection = null;
			
			try {
				connection = (HttpConnection)Connector.open(requestedResource.getUrl());
				connection.setRequestProperty("x-rim-gw-properties", "16.10");
				connection.setRequestProperty("x-rim-transcode-content", "*/*");
				connection.setRequestProperty("x-rim-accept-encoding", "yk;v=3;m=384");
				connection.setRequestProperty("x-wap-profile", "\"http://www.blackberry.net/go/mobile/profiles/uaprof/8320/4.5.0.rdf\"");
				connection.setRequestProperty("profile", "http://www.blackberry.net/go/mobile/profiles/uaprof/8320/4.5.0.rdf");
				connection.setRequestProperty("User-Agent", "BlackBerry8320/4.5.0.44 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/-1");
				connection.setRequestProperty("Accept", "application/vnd.rim.html,text/html,application/xhtml+xml,application/vnd.wap.xhtml+xml,application/vnd.wap.wmlc;q=0.9,application/vnd.wap.wmlscriptc;q=0.7,text/vnd.wap.wml;q=0.7,text/vnd.sun.j2me.app-descriptor,image/vnd.rim.png,image/jpeg,application/x-vnd.rim.pme.b,application/vnd.rim.ucs,image/gif;anim=1,application/vnd.rim.css;v=1,text/css;media=screen,*/*;q=0.5");
				connection.setRequestProperty("x-rim-original-accept", "application/vnd.rim.html,text/html,application/xhtml+xml,application/vnd.wap.xhtml+xml,application/vnd.wap.wmlc;q=0.9,application/vnd.wap.wmlscriptc;q=0.7,text/vnd.wap.wml;q=0.7,text/vnd.sun.j2me.app-descriptor,image/vnd.rim.png,image/jpeg,application/x-vnd.rim.pme.b,application/vnd.rim.ucs,image/gif;anim=1,application/vnd.rim.css;v=1,text/css;media=screen,*/*;q=0.");
				requestedResource.setHttpConnection(connection);
				browserContent.resourceReady(requestedResource);
			} catch (IOException e) {
			} finally {
				if (connection != null) {
					try { connection.close(); }
					catch (IOException e) {}
				}
			}
		}
	}
	
}

The Facebook Chat Facade.

/*
Copyright (c) 2010 E.Y. Baskoro

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
*/

package blackberry.facebook;

import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import javax.microedition.io.Connector;
import javax.microedition.io.SocketConnection;

import net.rim.device.api.collection.util.SortedReadableList;
import net.rim.device.api.crypto.MD5Digest;
import net.rim.device.api.system.Application;
import net.rim.device.api.util.StringComparator;
import net.sourceforge.jxa.Base64;
import net.sourceforge.jxa.XmlReader;
import net.sourceforge.jxa.XmlWriter;
import net.sourceforge.jxa.XmppListener;

/**
 * FacebookChatFacade
 * 
 * @author Eki Baskoro
 *
 */
public class FacebookChatFacade {

	private String resource = null;
	private FacebookFacade facebookFacade = null;
	SocketConnection connection = null;
	private XmlReader reader = null;
	private XmlWriter writer = null;
	private Vector listeners = new Vector();

	private static final String HOST = "chat.facebook.com";
	private static final int PORT = 5222;

	/**
	 * Default constructor.
	 * 
	 * @param facebookFacade injected Facebook Facade object.
	 */
	public FacebookChatFacade(FacebookFacade facebookFacade) {
		this.facebookFacade = facebookFacade;
	}

	/**
	 * Connect to the Chat service.
	 * 
	 */
	public void connect() {
		try {
			connection = (SocketConnection)Connector.open("socket://" + HOST + ":" + PORT + ";deviceSide=true");
			reader = new XmlReader(connection.openInputStream());
			writer = new XmlWriter(connection.openOutputStream());
			
			login();
			(new ReceiveThread()).start();
		} catch (Exception e) {
			fireOnConnFailed(e.toString());
		}
	}
	
	/**
	 * Disconnect from the Chat service.
	 * 
	 */
	public void disconnect() {
		if (connection != null) {
			try { connection.close(); } catch (IOException e) {}
		}
	}

	public void addListener(XmppListener listener) {
		if(listener != null && !listeners.contains(listener))
			listeners.addElement(listener);
	}

	public void removeListener(XmppListener listener) {
		if (listener != null)
			listeners.removeElement(listener);
	}

	public void login() throws IOException {
		writer.startTag("stream:stream");
		writer.attribute("xmlns", "jabber:client");
		writer.attribute("xmlns:stream", "http://etherx.jabber.org/streams");
		writer.attribute("to", HOST);
		writer.attribute("version", "1.0");
		writer.flush();
		
		do { reader.next(); }
		while (reader.getType() != XmlReader.END_TAG || !reader.getName().equals("stream:features"));
		
		writer.startTag("auth");
		writer.attribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
		writer.attribute("mechanism", "X-FACEBOOK-PLATFORM");
		writer.endTag();
		writer.flush();		
		
		String encodedChallenge = null;
		
		do {
			reader.next();
			
			if (reader.getType() == XmlReader.TEXT)
				encodedChallenge = reader.getText();
		} while (reader.getType() != XmlReader.END_TAG || !reader.getName().equals("challenge"));
		
		String challenge = new String(Base64.decode(encodedChallenge));
		Hashtable data = new Hashtable();
		int startIndex = 0;
		int stopIndex = 0;
		
		do {
			stopIndex = challenge.indexOf('&', startIndex);
			stopIndex = ((stopIndex > -1)? stopIndex : challenge.length());
			String nvpair = challenge.substring(startIndex, stopIndex);
			int index = nvpair.indexOf('=');
			String name = nvpair.substring(0, index);
			String value = nvpair.substring(index + 1);
			
			if (name.equals("method") || name.equals("nonce")) {
				data.put(name, value);
			}
			
			startIndex = stopIndex + 1;
		} while (stopIndex < challenge.length());
		
		data.put("api_key", facebookFacade.getApplicationKey());
		data.put("call_id", String.valueOf(System.currentTimeMillis()));
		data.put("v", "1.0");
		data.put("session_key", facebookFacade.getSessionKey());
		data.put("sig", getSignature(data, facebookFacade.getSessionSecret()));
		
		StringBuffer response = new StringBuffer();
		Enumeration keys = data.keys();
		
		while (keys.hasMoreElements()) {
			String key = (String)keys.nextElement();
			response.append(key).append('=').append(data.get(key));
			
			if (keys.hasMoreElements())
				response.append('&');
		}
		
		writer.startTag("response");
		writer.attribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl");
		writer.text(Base64.encode(response.toString().getBytes()));
		writer.endTag();
		writer.flush();
		
		do { reader.next(); }
		while (reader.getType() != XmlReader.END_TAG);
		
		if (!reader.getName().equals("success")) {
			fireOnAuthFailed(reader.getName() + ", failed authentication");
			
			return;
		}
		
		writer.startTag("stream:stream");
		writer.attribute("xmlns", "jabber:client");
		writer.attribute("xmlns:stream", "http://etherx.jabber.org/streams");
		writer.attribute("to", HOST);
		writer.attribute("version", "1.0");
		writer.flush();
		
		do { reader.next(); }
		while (reader.getType() != XmlReader.END_TAG || !reader.getName().equals("stream:features"));
		
		writer.startTag("iq");
		writer.attribute("type", "set");
		writer.attribute("id", "res_binding");
		writer.startTag("bind");
		writer.attribute("xmlns", "urn:ietf:params:xml:ns:xmpp-bind");
		
		if (resource != null) {
			writer.startTag("resource");
			writer.text(resource);
			writer.endTag();
		}
		
		writer.endTag();
		writer.endTag();
		writer.flush();
		
		do {
			if (reader.next() == XmlReader.TEXT) {
				String jid = reader.getText();
				resource = jid.substring(jid.indexOf('/') + 1);
			}
		} while (reader.getType() != XmlReader.END_TAG || !reader.getName().equals("iq"));
		
		fireOnAuth(resource);
	}

	public void logoff() {
		try {
			writer.endTag();
			writer.flush();
			writer.close();
		} catch (final Exception e) {
			fireOnConnFailed();
		}
	}

	public void sendMessage(String to, String message) {
		try {
			writer.startTag("message");
			writer.attribute("type", "chat");
			writer.attribute("to", to);
			writer.startTag("body");
			writer.text(message);
			writer.endTag();
			writer.endTag();
			writer.flush();
		} catch (Exception e) {
			fireOnConnFailed();
		}
	}

	private void sendPresence(final String to, final String type, final String show, final String status, final int priority) {
		try {
			writer.startTag("presence");
			
			if (type != null) {
				writer.attribute("type", type);
			}
			
			if (to != null) {
				writer.attribute("to", to);
			}
			
			if (show != null) {
				writer.startTag("show");
				writer.text(show);
				writer.endTag();
			}
			
			if (status != null) {
				writer.startTag("status");
				writer.text(status);
				writer.endTag();
			}
			
			if (priority != 0) {
				writer.startTag("priority");
				writer.text(String.valueOf(priority));
				writer.endTag();
			}
			
			writer.endTag(); // presence
			writer.flush();
		} catch (Exception e) {
			fireOnConnFailed();
		}
	}

	public void setStatus(String show, String status, final int priority) {
		if (show.equals("")) {
			show = null;
		}
		
		if (status.equals("")) {
			status = null;
		}
		
		if (show.equals("invisible")) {
			sendPresence(null, "invisible", null, null, priority);
		} else {
			sendPresence(null, null, show, status, priority);
		}
	}

	public void subscribe(String to) {
		sendPresence(to, "subscribe", null, null, 0);
	}

	public void unsubscribe(String to) {
		sendPresence(to, "unsubscribe", null, null, 0);
	}

	public void subscribed(String to) {
		sendPresence(to, "subscribed", null, null, 0);
	}

	public void unsubscribed(String to) {
		sendPresence(to, "unsubscribed", null, null, 0);
	}

	public void saveContact(String jid, String name, Enumeration group, String subscription) {
		try {
			writer.startTag("iq");
			writer.attribute("type", "set");
			writer.startTag("query");
			writer.attribute("xmlns", "jabber:iq:roster");
			writer.startTag("item");
			writer.attribute("jid", jid);
			
			if (name != null) {
				writer.attribute("name", name);
			}
			
			if (subscription != null) {
				writer.attribute("subscription", subscription);
			}
			
			if (group != null) {
				while (group.hasMoreElements()) {
					writer.startTag("group");
					writer.text((String)group.nextElement());
					writer.endTag(); // group
				}
			}
			
			writer.endTag(); // item
			writer.endTag(); // query
			writer.endTag(); // iq
			writer.flush();
		} catch (Exception e) {
			fireOnConnFailed();
		}
	}

	public void getRoster() throws IOException {
		writer.startTag("iq");
		writer.attribute("id", "roster");
		writer.attribute("type", "get");
		writer.startTag("query");
		writer.attribute("xmlns", "jabber:iq:roster");
		writer.endTag(); // query
		writer.endTag(); // iq
		writer.flush();
	}

	public void getVCard() throws IOException {
		writer.startTag("iq");
		writer.attribute("id", "1");
		writer.attribute("type", "get");
		writer.startTag("vCard");
		writer.attribute("xmlns", "vcard-temp");
		writer.endTag();
		writer.endTag();
		writer.flush();
	}
	
	private void parseIq() throws IOException {
		String type = reader.getAttribute("type");
		String id = reader.getAttribute("id");
		String from = reader.getAttribute("from");
		
		if (type.equals("error")) {
			while (reader.next() == XmlReader.START_TAG) {
				if (reader.getName().equals("error")) {
					String code = reader.getAttribute("code");
					fireOnAuthFailed(code + ": " + parseText());
				} else {
					parseText();
				}
			}
		} else {
			while (reader.next() == XmlReader.START_TAG) {
				if (reader.getName().equals("query")) {
					if (reader.getAttribute("xmlns").equals("jabber:iq:roster")) {
						while (reader.next() == XmlReader.START_TAG) {
							if (reader.getName().equals("item")) {
								type = reader.getAttribute("type");
								String jid = reader.getAttribute("jid");
								String name = reader.getAttribute("name");
								String subscription = reader.getAttribute("subscription");
								boolean check = true;
								
								while (reader.next() == XmlReader.START_TAG) {
									if (reader.getName().equals("group")) {
										fireOnContact(jid, name, parseText(), subscription);
										check = false;
									} else {
										parseIgnore();
									}
								}
								
								if (check) {
									fireOnContact(jid, name, "", subscription);
								}
							} else {
								parseIgnore();
							}
						}
						
						fireOnContactOver();
					} else if (reader.getAttribute("xmlns").equals("jabber:iq:version")) {
						while (reader.next() == XmlReader.START_TAG) {
							parseIgnore();
						}
						
						writer.startTag("iq");
						writer.attribute("type", "result");
						writer.attribute("id", id);
						writer.attribute("to", from);
						writer.startTag("query");
						writer.attribute("xmlns", "jabber:iq:version");
						writer.startTag("name");
						writer.text("FBBC");
						writer.endTag();
						writer.startTag("version");
						writer.text("1.0");
						writer.endTag();
						writer.startTag("os");
						writer.text("BlackBerry");
						writer.endTag();
						writer.endTag(); // query
						writer.endTag(); // iq
					} else {
						parseIgnore();
					}
				} else {
					parseIgnore();
				}
			}
		}
	}

	private void parsePresence() throws IOException {
		String from = reader.getAttribute("from");
		String type = reader.getAttribute("type");
		String status = "";
		String show = "";
		
		while (reader.next() == XmlReader.START_TAG) {
			String tmp = reader.getName();
			
			if (tmp.equals("status")) {
				status = parseText();
			} else if (tmp.equals("show")) {
				show = parseText();
			} else {
				parseIgnore();
			}
		}

		if (type == null) {
			fireOnStatus(from, show, status);
		} else {
			if (type.equals("unsubscribed") || type.equals("error")) {
				fireOnUnsubscribe(from);
			} else if (type.equals("subscribe")) {
				fireOnSubscribe(from);
			} else if (type.equals("unavailable")) {
				fireOnStatus(from, "na", status);
			}
		}
	}

	private void parseMessage() throws IOException {
		String from = reader.getAttribute("from");
		String type = reader.getAttribute("type");
		String body = null;
		String subject = null;
		
		while (reader.next() == XmlReader.START_TAG) {
			String tmp = reader.getName();
			
			if (tmp.equals("body")) {
				body = parseText();
			} else if (tmp.equals("subject")) {
				subject = parseText();
			} else {
				parseIgnore();
			}
		}
		
		fireOnMessage((from.indexOf('/') == -1) ? from : from.substring(0, from.indexOf('/')), body);
	}

	private String parseText() throws IOException {
		String endTagName = reader.getName();
		StringBuffer str = new StringBuffer("");
		int t = reader.next(); // omit start tag
		
		while (!endTagName.equals(reader.getName())) {
			if (t == XmlReader.TEXT) {
				str.append(reader.getText());
			}
			
			t = reader.next();
		}
		return str.toString();
	}

	private void parseIgnore() throws IOException {
		int x;
		
		while ((x = reader.next()) != XmlReader.END_TAG) {
			if (x == XmlReader.START_TAG)
				parseIgnore();
		}
	}
	
	private void fireOnAuth(final String resource) {
		Application.getApplication().invokeLater(new Runnable() {
			
			public void run() {
				Enumeration e = listeners.elements();
				
				while (e.hasMoreElements()) {
					XmppListener listener = (XmppListener)e.nextElement();
					listener.onAuth(resource);
				}
			}
		});
	}
	
	private void fireOnAuthFailed(final String message) {
		Application.getApplication().invokeLater(new Runnable() {
			
			public void run() {
				Enumeration e = listeners.elements();
				
				while (e.hasMoreElements()) {
					XmppListener listener = (XmppListener)e.nextElement();
					listener.onAuthFailed(message);
				}
			}
		});
	}

	private void fireOnStatus(final String jid, final String show, final String status) {
		Application.getApplication().invokeLater(new Runnable() {
			
			public void run() {
				Enumeration e = listeners.elements();
				
				while (e.hasMoreElements()) {
					XmppListener listener = (XmppListener)e.nextElement();
					listener.onStatusEvent(jid, show, status);
				}
			}
		});
	}
	
	private void fireOnSubscribe(final String jid) {
		Application.getApplication().invokeLater(new Runnable() {
			
			public void run() {
				Enumeration e = listeners.elements();
				
				while (e.hasMoreElements()) {
					XmppListener listener = (XmppListener)e.nextElement();
					listener.onSubscribeEvent(jid);
				}
			}
		});
	}
	
	private void fireOnUnsubscribe(final String jid) {
		Application.getApplication().invokeLater(new Runnable() {
			
			public void run() {
				Enumeration e = listeners.elements();
				
				while (e.hasMoreElements()) {
					XmppListener listener = (XmppListener)e.nextElement();
					listener.onUnsubscribeEvent(jid);
				}
			}
		});
	}
	
	private void fireOnContact(final String jid, final String name, final String group, final String subscription) {
		Application.getApplication().invokeLater(new Runnable() {
			
			public void run() {
				Enumeration e = listeners.elements();
				
				while (e.hasMoreElements()) {
					XmppListener listener = (XmppListener)e.nextElement();
					listener.onContactEvent(jid, name, group, subscription);
				}
			}
		});
	}
	
	private void fireOnContactOver() {
		Application.getApplication().invokeLater(new Runnable() {
			
			public void run() {
				Enumeration e = listeners.elements();
				
				while (e.hasMoreElements()) {
					XmppListener listener = (XmppListener)e.nextElement();
					listener.onContactOverEvent();
				}
			}
		});
	}
	
	private void fireOnMessage(final String from, final String message) {
		Application.getApplication().invokeLater(new Runnable() {
			
			public void run() {
				Enumeration e = listeners.elements();
				
				while (e.hasMoreElements()) {
					XmppListener listener = (XmppListener)e.nextElement();
					listener.onMessageEvent(from, message);
				}
			}
		});
	}
	
	private void fireOnConnFailed() {
		fireOnConnFailed("");
	}
	
	private void fireOnConnFailed(final String message) {
	    writer.close();
	    reader.close();
	    
	    Application.getApplication().invokeLater(new Runnable() {
	    	
	    	public void run() {
	    		Enumeration e = listeners.elements();
	    	    
	    	    while (e.hasMoreElements()) {
	    	    	XmppListener listener = (XmppListener)e.nextElement();
	    	    	listener.onConnFailed(message);
	    	    }
	    	}
	    });
	}
	
	private static String getSignature(Hashtable arguments, String secret) {
		try {
			SortedReadableList keysList = new SortedReadableList(StringComparator.getInstance(true));
			keysList.loadFrom(arguments.keys());
			keysList.sort();
			StringBuffer requestString = new StringBuffer();
			
			for (int i = 0; i < keysList.size(); i ++) {
				String key = (String)keysList.getAt(i);
				String val = (String)arguments.get(key);
				requestString.append(key + "=" + val);
			}
			
			requestString.append(secret);
			
			MD5Digest digest = new MD5Digest();
			digest.update(requestString.toString().getBytes("iso-8859-1"), 0, requestString.length());
			byte[] digestResult = digest.getDigest();
			
			return convertToHex(digestResult);
		} catch (IOException e) {
			return null;
		}
	}
	
	private static String convertToHex(byte[] data) {
        StringBuffer buf = new StringBuffer();
        
        for (int i = 0; i < data.length; i++) {
            int halfbyte = (data[i] >>> 4) & 0x0F;
            int two_halfs = 0;
            
            do {
                if ((0 <= halfbyte) && (halfbyte <= 9))
                    buf.append((char) ('0' + halfbyte));
                else
                    buf.append((char) ('a' + (halfbyte - 10)));
                
                halfbyte = data[i] & 0x0F;
            } while (two_halfs++ < 1);
        }
        
        return buf.toString();
    }
	
	private class ReceiveThread extends Thread {
		
		public void run() {
			try {
				while (reader.next() == XmlReader.START_TAG) {
					String tagName = reader.getName();
					
					if (tagName.equals("message")) {
						parseMessage();
					} else if (tagName.equals("presence")) {
						parsePresence();
					} else if (tagName.equals("iq")) {
						parseIq();
					} else {
						parseIgnore();
					}
				}
				
				reader.close();
			} catch (IOException e) {
				fireOnConnFailed(e.getMessage());
			}
		}
	}
}

XmlReader class.

/*
 * Copyright 2004 Grzegorz Grasza groz@gryf.info
 * 
 * This file is part of mobber. Mobber is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version. Mobber is distributed in the hope that
 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details. You should have received a copy of
 * the GNU General Public License along with mobber; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA .
 */

package net.sourceforge.jxa;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Stack;

import net.rim.device.api.system.Application;

/**
 * XML-Reader
 * 
 * @author Grzegorz Grasza
 * @version 1.0
 * @since 1.0
 */
public class XmlReader {
	
	public final static int START_DOCUMENT = 0;
	public final static int END_DOCUMENT = 1;
	public final static int START_TAG = 2;
	public final static int END_TAG = 3;
	public final static int TEXT = 4;

	private InputStream inputStream;
	private Stack tags = new Stack();
	private boolean inside_tag = false;
	private String tagName;
	private String text;
	private final Hashtable attributes = new Hashtable();
	private int c;
	private int type = START_DOCUMENT;
	
	public XmlReader(InputStream inputStream) throws IOException {
		this.inputStream = inputStream;
	}
	
	public int getType() {
		return type;
	}

	public String getName() {
		return tagName;
	}

	public String getAttribute(String name) {
		return (String)attributes.get(name);
	}

	public Enumeration getAttributes() {
		return attributes.keys();
	}

	public String getText() {
		return text;
	}

	public Enumeration getTags() {
		return tags.elements();
	}
	
	public void close() {
		try {
			inputStream.close();
		} catch (IOException e) {}
	}
	
	public int next() throws IOException {
		c = getNextCharacter();
		
		if (c <= ' ')
			while ((c = getNextCharacter()) <= ' ' && c != -1);
		
		if (c == -1) {
			type = END_DOCUMENT;
			
			return type;
		}

		if (c == '<' || (c == '/' && !inside_tag)) {
			inside_tag = true;
			tagName = null;
			text = null;
			attributes.clear();

			if (c == '<') {
				c = getNextCharacter();
			}
			
			if (c == '/') {
				type = END_TAG;
				c = getNextCharacter();
				tagName = readName('>');
			} else if (c == '?' || c == '!') { // ignore xml heading & // comments 
				while ((c = getNextCharacter()) != '>');
				
				next();
			} else {
				type = START_TAG;
				tagName = readName(' ');
				tags.addElement(tagName);
				String attribute = "";
				String value = "";
				
				while (c == ' ') {
					c = getNextCharacter();
					attribute = readName('=');
					int quote = getNextCharacter(); // '''
					c = getNextCharacter();
					value = readText(quote); //change from value = this.readText(''');
					c = getNextCharacter();
					attributes.put(attribute, value);
				}
				
				if (c != '/') {
					inside_tag = false;
				}
			}
		} else if (c == '>' && inside_tag) { // last tag ended
			type = END_TAG;
			inside_tag = false;
		} else {
			tagName = null;
			attributes.clear();
			type = TEXT;
			text = readText('<');
		}

		return type;
	}

	//http://discussion.forum.nokia.com/forum/showthread.php?t=76814
	//by abirr
	private int getNextCharacter() throws IOException {
		int a = inputStream.read();
		int t = a;
		
		if ((t|0xC0) == t) {
			int b = inputStream.read();
			
			if (b == 0xFF) { // Check if legal
				t = -1;
			} else if (b < 0x80) { // Check for UTF-8 compliancy
				throw new IOException("Bad UTF-8 Encoding encountered 1");
			} else if ((t|0xE0) == t) {
				int c = inputStream.read();
				
				if (c == 0xFF) { // Check if legal
					t = -1;
				} else if (c < 0x80) { // Check for UTF-8 compliancy
					throw new IOException("Bad UTF-8 Encoding encountered 2");
				} else {
					t = ((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F);
				}
			} else {
				t = ((a & 0x1F) << 6) | (b & 0x3F);
			}
		}
		
		return t;
	}
	
	private String readText(int end) throws IOException {
		StringBuffer output = new StringBuffer();
		
		while (c != end) {
			if (c == '&') {
				c = getNextCharacter();
				
				switch (c) {
				
					case 'l':
						output.append('<');
						break;
						
					case 'g':
						output.append('>');
						break;
						
					case 'a':
						if (getNextCharacter() == 'm') {
							output.append('&');
						} else {
							output.append('\'');
						}
						
						break;
						
					case 'q':
						output.append('"');
						break;
						
					case 'n':
						output.append(' ');
						break;
						
					default:
						output.append('?');
						break;
				}

				while ((c = getNextCharacter()) != ';');
				
			} else if (c == '\\') {
				if ((c = getNextCharacter()) == '<')
					break;

				output.append((char)c);
			} else {
				output.append((char)c);
			}
			
			c = getNextCharacter();
		}
		
		return output.toString();
	}

	private String readName(int end) throws IOException {
		StringBuffer output = new StringBuffer("");
		
		do {
			output.append((char)c);
		} while ((c = getNextCharacter()) != end && c != '>' && c != '/');
		
		return output.toString();
	}
	
	private void DEBUG(final String message) {
		Application.getApplication().invokeAndWait(new Runnable() {
			public void run() { net.rim.device.api.ui.component.Dialog.alert(message); }
		});
	}
	
};

XmlWriter class.

/*
 * Copyright 2004 Grzegorz Grasza groz@gryf.info
 * 
 * This file is part of mobber. Mobber is free software; you can redistribute it
 * and/or modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the License,
 * or (at your option) any later version. Mobber is distributed in the hope that
 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details. You should have received a copy of
 * the GNU General Public License along with mobber; if not, write to the Free
 * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA .
 */

package net.sourceforge.jxa;

import java.io.*;
import java.util.*;

/**
 * XML-Writer
 * 
 * @author Grzegorz Grasza
 * @version 1.0
 * @since 1.0
 */
public class XmlWriter {
	
	private OutputStreamWriter writer;
	private Stack tags;
	boolean inside_tag;

	public XmlWriter(OutputStream out) throws UnsupportedEncodingException {
		writer = new OutputStreamWriter(out, "UTF-8");
		tags = new Stack();
		inside_tag = false;
	}
	
	public void close() {
		try {
			writer.close();
		} catch (IOException e) {}
	}

	public void flush() throws IOException {
		if (inside_tag) {
			writer.write('>'); // prevent Invalid XML fatal error
			inside_tag = false;
		}
		
		writer.flush();
	}

	public void startTag( String tag) throws IOException {
		if (inside_tag) {
			writer.write('>');
		}

		writer.write('<');
		writer.write(tag);
		tags.push(tag);
		inside_tag = true;
	}

	public void attribute(String atr, String value) throws IOException {
		if (value == null)
			return;
		
		writer.write(' ');
		writer.write(atr);
		writer.write("=\'");
		writeEscaped(value);
		writer.write('\'');
	}

	public void endTag() throws IOException {
		try {
			String tagname = (String)tags.pop();
			
			if (inside_tag) {
				writer.write("/>");
				inside_tag = false;
			} else {
				writer.write("');
			}
		} catch (EmptyStackException e) {}
	}

	public void text(String str) throws IOException {
		if (inside_tag) {
			writer.write('>');
			inside_tag = false;
		}
		
		writeEscaped(encodeUTF(str));
	}

	private void writeEscaped(String str) throws IOException {
		for (int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);
			
			switch (c) {
				case '<':
					writer.write("<");
					break;
					
				case '>':
					writer.write(">");
					break;
					
				case '&':
					writer.write("&");
					break;
					
				case '\'':
					writer.write("&apos;");
					break;
					
				case '"':
					writer.write(""");
					break;
					
				default:
					writer.write(c);
					break;
			}
		}
	}

	private String encodeUTF(String str) {
		try {
			String utf = new String(str.getBytes("UTF-8"));
			
			return utf;
		} catch (UnsupportedEncodingException e) {
			return null;
		}
	}
};

XmppListener class.

/*
 * Copyright 2006 Swen Kummer, Dustin Hass, Sven Jost
 * modified by Yuan-Chu Tai
 * http://jxa.sourceforge.net/
 * 
 * This is free software; you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version. Mobber is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details. You should have received a copy of the GNU General Public License
 * along with mobber; if not, write to the Free Software Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA 02111-1307 USA .
 */

package net.sourceforge.jxa;

/**
 * Interface class to implement events
 * 
 * @author Swen Kummer, Dustin Hass, Sven Jost
 * @version 2.0
 * @since 1.0
 */
public interface XmppListener {

	//public void onDebug(final String msg);
	/**
	 * This event is sent when a parser or connection error occurs.
	 */
	public void onConnFailed(final String msg);

	/**
	 * This event occurs when the login/authentication process succeeds.
	 */
	public void onAuth(final String resource);

	/**
	 * This event occurs when the login/authentication process fails.
	 * 
	 * @param message some error information
	 */
	public void onAuthFailed(final String message);

	/**
	 * This event is sent when a message arrives.
	 * 
	 * @param from the jid of the sender
	 * @param body the message text
	 */
	public void onMessageEvent(final String from, final String body);

	/**
	 * This event occurs when someone has removed you from his roster (o rly?)
	 * 
	 * @param jid the jid of the remover
	 */
	//public void onContactRemoveEvent(final String jid);

	/**
	 * This event occurs for each contact in roster when the roster is queried.
	 * 
	 * @param jid the jid of the contact
	 * @param name the nickname of the contact
	 * @param group the group in which the contact is saved
	 * @param subscription the subscription status of the contact
	 */
	public void onContactEvent(final String jid, final String name, final String group, final String subscription);

	public void onContactOverEvent();
	/**
	 * 

* This event occurs when a presence message comes from jabber server. This * can also be your own jid. The presence can be one of the following: *

* *
    *
  • blank: user is online
  • *
  • chat: user is free to chat
  • *
  • away: user is away
  • *
  • xa: user is not available (extended away).
  • *
  • dnd: user is busy (do not disturb).
  • *
* *

* An offline user will send no status message at all. *

* * @param jid the JID of the contact that changed his status * @param status the display status */ public void onStatusEvent(final String jid, final String show, final String status); /** * This event is sent when a subscription request arrives. This means * someone has allowed you to see his status. * * @param jid the jid of the one who wants to subscribe to you */ public void onSubscribeEvent(final String jid); /** * This event is sent when a subscription remove event arrives. This means * someone has taken away your right to see his status. * * @param jid the jid of the one who removes your subscription */ public void onUnsubscribeEvent(final String jid); };

Base64 class.

// Copyright (C) 2001 Stefan Haustein, Oberhausen (Rhld.), Germany
//
// Contributors: Stefan Haustein
//
// License: LGPL
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation; either version 2.1 of
// the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
// USA

package net.sourceforge.jxa;

import java.io.*;

public class Base64 {

    static final char[] charTab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray (); 

    /**
     * Create a base64 encoded string. 
     * @param data
     * @return encoded
     */
    public static String encode(byte [] data) {
        return encode (data, 0, data.length, null).toString ();
    }

    /** Encodes the part of the given byte array denoted by start and
        len to the Base64 format.  The encoded data is appended to the
        given StringBuffer. If no StringBuffer is given, a new one is
        created automatically. The StringBuffer is the return value of
        this method.
        @param data
        @param start
        @param len
        @param buf
        @return Encoded string
    */
    public static StringBuffer encode(byte [] data, int start, int len, StringBuffer buf) {
        if (buf == null) 
            buf = new StringBuffer (data.length * 3 / 2);

        int end = len - 3;
        int i = start;
        int n = 0;

        while (i <= end) {
            int d = ((( data [i]) & 0x0ff) << 16) 
                | ((( data [i+1]) & 0x0ff) << 8)
                | (( data [i+2]) & 0x0ff);

            buf.append (charTab [(d >> 18) & 63]);
            buf.append (charTab [(d >> 12) & 63]);
            buf.append (charTab [(d >> 6) & 63]);
            buf.append (charTab [d & 63]);

            i += 3;

            if (n++ >= 14) {
                n = 0;
                buf.append ("\r\n");
            }
        }

        if (i == start + len - 2) {
            int d = ((( data [i]) & 0x0ff) << 16) 
                | ((( data [i+1]) & 255) << 8);

            buf.append (charTab [(d >> 18) & 63]);
            buf.append (charTab [(d >> 12) & 63]);
            buf.append (charTab [(d >> 6) & 63]);
            buf.append ("=");
        } else if (i == start + len - 1) {
            int d = (( data [i]) & 0x0ff) << 16;

            buf.append (charTab [(d >> 18) & 63]);
            buf.append (charTab [(d >> 12) & 63]);
            buf.append ("==");
        }

        return buf;
    }
    
    static int decode(char c) {
        if (c >= 'A' && c <= 'Z') {
            return ( c) - 65;
        } else if (c >= 'a' && c <= 'z') { 
            return ( c) - 97 + 26;
        } else if (c >= '0' && c <= '9') {
            return (c) - 48 + 26 + 26;
        } else {
        	switch (c) {
        		case '+':
        			return 62;
        			
        		case '/':
        			return 63;
        			
        		case '=':
        			return 0;
        			
        		default:
        			throw new RuntimeException (new StringBuffer("unexpected code: ").append(c).toString());
        	}
        }
    }
    
    /** Decodes the given Base64 encoded String to a new byte array. 
        The byte array holding the decoded data is returned.
        @param s
        @return The real thingi
     */
    public static byte[] decode(String s) {

        int i = 0;
        ByteArrayOutputStream bos = new ByteArrayOutputStream ();
        int len = s.length();
        
        while (true) { 
            while (i < len && s.charAt(i) <= ' ')
            	i++;

            if (i == len)
            	break;

            int tri = (decode(s.charAt(i)) << 18)
                + (decode(s.charAt(i+1)) << 12)
                + (decode(s.charAt(i+2)) << 6)
                + (decode(s.charAt(i+3)));
            
            bos.write((tri >> 16) & 255);
            
            if (s.charAt(i+2) == '=')
            	break;
            
            bos.write((tri >> 8) & 255);
            
            if (s.charAt(i+3) == '=')
            	break;
            
            bos.write(tri & 255);

            i += 4;
        }
        
        return bos.toByteArray ();
    }
    
}

Comments