Thursday 27 January 2011

Android - OutOfMemoryError: bitmap size exceeds VM budget

When you see this error in your console, you're most certainly using some large bitmaps in your application which are taking up a of memory (especially T-Mobile G1, limited to 16 MB).

The solution to this problem is to create a temp storage and pass it to BitmapFactory before decoding. Take into consideration that this error isn't necessarily triggered when handling bitmaps programmatically, but also when specifying them through xml.

If you do specify the bitmaps through xml, for example a background image for a RelativeLayout, just remove the background declaration from xml, and declare the background programmatically, after properly decoding the bitmap.

So, here's the code:


BitmapFactory.Options options = new BitmapFactory.Options();
options.inTempStorage = new byte[16*1024];
     
Bitmap bitmapImage = BitmapFactory.decodeResource(getResources(), R.drawable.background, options);

If you want to set this bitmap as background, just create a BitmapDrawable, and set it as background:


BitmapDrawable bitmapImageDrawable = new BitmapDrawable(bitmapImage);
RelativeLayout yourRelLayout = (RelativeLayout) findViewById(R.id.YourRelativeLayoutId);
yourRelLayout.setBackgroundDrawable(bitmapImageDrawable);

Also, you might want to tile you image when setting it as background, so you'll need to do this before calling setBackgroundDrawable


bitmapImageDrawable.setTileModeX(Shader.TileMode.REPEAT);
bitmapImageDrawable.setTileModeY(Shader.TileMode.REPEAT);

Don't forget to call bitmapImage.recycle() after usage.

Monday 17 January 2011

Funny Violence BlackBerry Version - part 1

Funny Violence is a BlackBerry device application developed on Koprol API. The basic functionality is this:

  • 1. The user logs into the application with his Koprol credentials.
  • 2. He has the ability to search for a specific location or to choose one from the “Top Places Nearby” section
  • 3. The user has a range of funny weapons.
  • 4. On the selected place he sees all users that checked in.
  • 5. He can "Attack" his friends on Koprol with these weapons.
  • 6. The user can select a weapon at any time.
  • 7. After the attack, a stream is published under the specific location in the form “@attacked_user, you have just been sheep'ed by @attacker_user' at specific_location”.


One of the first things that this application needed was a library for XAuth, because the BlackBerry device cannot play the HTTP re-direct game normally played in the "3-legged" OAuth model. As there isn't a BlackBerry library for XAuth, Oauth Signpost for BlackBerry needed to be adapted.

Now, the Signpost source files, under oauth → signpost → basic there's a file called DefaultOAuthProvider.java which extends AbstractOAuthProvider. We'll talk about AbstractOAuthProvider later. In DefaultOAuthProvider.java, you'll se that the request is created and different request properties are set. Now, we'll create a new class, DefaultXAuthProvider.java, which will extend AbstractXAuthProvider (later in this post). The first thing we'll change is the constructor. Oauth uses requestTokenEndpointUrl, String accessTokenEndpointUrl and authorizationWebsiteUrl as parameters (see Oauth docs). For XAuth we need requestTokenEndPointUrl, which is bassically the url that accesses the access token directly (XAuth basically short-circuits Oauth) and the username and password used to login into the target server. So the code will look like this:

public DefaultXAuthProvider(String requestTokenEndpointUrl, String username,
            String password) {
        super(requestTokenEndpointUrl, username, password);
}

Now, let's set some request parameters. They may vary given the service provider, but in this case (Koprol), we need to set the following:

connection.setRequestProperty("Connection", "keep-alive");
connection.setRequestProperty("Content-type", "application/x-www-form-urlencoded");

Now, let's move on to oauth → signpost → signature in the Signpost source codes and open the AuthorizationHeaderSigningStrategy.java. Here, the request is signed and the parameters are added to the request header. We'll add some extra parameters to the header (according to XAuth docs): XAUTH_MODE, XAUTH_USERNAME and XAUTH_PASSWORD. I've defined these values in oauth → signpost → Oauth.java. I'll paste the modification from this file too, but first, our new request parameters:

if (requestParameters.containsKey(OAuth.XAUTH_MODE)) {
        sb.append(", ");
        sb.append(requestParameters.getAsHeaderElement(OAuth.XAUTH_MODE));
}
if (requestParameters.containsKey(OAuth.XAUTH_USERNAME)) {
        sb.append(", ");
        sb.append(requestParameters.getAsHeaderElement(OAuth.XAUTH_USERNAME));
}
if (requestParameters.containsKey(OAuth.XAUTH_PASSWORD)) {
        sb.append(", ");
        sb.append(requestParameters.getAsHeaderElement(OAuth.XAUTH_PASSWORD));
}

The previous code goes right after

sb.append(OAuth.toHeaderElement(OAuth.OAUTH_SIGNATURE, signature));

Now, the modifications from Oauth.java:

public static final String XAUTH_MODE = "x_auth_mode";
public static final String XAUTH_MODE_VALUE = "client_auth";
public static final String XAUTH_USERNAME = "x_auth_username";
public static final String XAUTH_PASSWORD = "x_auth_password";

Ok, let's move on to AbstractOauthProvider.java which we talked about at the beginning of this post. You'll find it under oauth → signpost. Here, normally, the token is requested and retrieved. Of course, for XAuth we need to make some changes, so, we'll create a new file AbstractXauthProvider.java, based on AbstractOauthProvider.java.

Here, we only need accessTokenEndpointUrl, username and password so we can get rid of requestTokenEndpointUrl and authorizationWebsiteUrl. Next, get rid of callbackUrl parameter from retrieveRequestToken method.

Next, retrieveRequestToken will change to retrieveAccessToken with consumer as parameter. Inside this method we'll call the retrieveToken method which will be modified as follows: after

if (this.listener != null) {
        this.listener.prepareSubmission(request);
}

we'll add:

HttpConnection connection = (HttpConnection)request.unwrap();
            
StringBuffer requestBody = new StringBuffer();
requestBody.append(OAuth.XAUTH_MODE + "=" + OAuth.XAUTH_MODE_VALUE + "&");
requestBody.append(OAuth.XAUTH_USERNAME + "=" + username + "&");
requestBody.append(OAuth.XAUTH_PASSWORD + "=" + password);
byte[] body = requestBody.toString().getBytes();
connection.setRequestProperty("Content-Length", "" + body.length);
         
OutputStream os = null;
try {
    os = connection.openOutputStream();
    os.write(body);
} finally {
    try{
        if(os != null) {
            os.close();
        }
} catch(IOException e) {}
}

We created a new connection and added the specific XAuth parameters.

Finally, we're handling the exceptions:

public String getAuthorizationWebsiteUrl() {
        throw new RuntimeException("Method is not supported");
    }   
    public String getRequestTokenEndpointUrl() {
        throw new RuntimeException("Method is not supported");
    }   
    public void retrieveAccessToken(OAuthConsumer consumer, String oauthVerifier)
            throws OAuthMessageSignerException, OAuthNotAuthorizedException,
            OAuthExpectationFailedException, OAuthCommunicationException {
        throw new RuntimeException("Method is not supported");
    }   
    public String retrieveRequestToken(OAuthConsumer consumer,
            String callbackUrl) throws OAuthMessageSignerException,
            OAuthNotAuthorizedException, OAuthExpectationFailedException,
            OAuthCommunicationException {
        throw new RuntimeException("Method is not supported");
    }   
    public void setOAuth10a(boolean isOAuth10aProvider) {
        throw new RuntimeException("Method is not supported");
    }

Finally, there's a very important configuration that we need to make in order for the headers to not be altered prior their arrival to the server. Edit the following file: C:\Program Files\Research In Motion\BlackBerry Email and MDS Services
Simulators\MDS\config\rimpublic.property (the location may vary depending on your installation path. In my case, the path is: C:\eclipse\plugins\net.rim.ejde.componentpack5.0.0_5.0.0.25\components\MDS\config\rimpublic.property) and change this line:

application.handler.http.AuthenticationSupport = true

so it'll look like this:

application.handler.http.AuthenticationSupport = false

End of Part 1. Exciting continuations coming to you soon

External JAR problems on BlackBerry projects under Eclipse


I've been recently trying to parse some external XML's and for this I wanted to user the KXML2 library. But I came across a pretty nasty problem.

To be able to use this library you obviously need to add the jar file on your project. Everything ok until BlackBerry signals that it couldn't find the jar file specified. I wasn't the only one with this problem (see http://www.thinkingblackberry.com/archives/204) so it wasn't something I did wrong.

Anyway, there's a simple solution for this shortcoming. Here are the steps:

1. Download the jar file

2. Preverify the .jar file

This can be done from the command line. Before this, be sure you have java set in your environment variables (in my case “C:\Program Files\Java\jdk1.6.0_20\bin”). Now, copy your .jar file in JDE_HOME/bin and run the following command:

preverify.exe -classpath ..\lib\net_rim_api.jar your_jar_file_name.jar

Where JDE_HOME is the BB JDE home directory or the component pack home directory. The “preverify” command will generate the file in the “output” folder in JDE_HOME/bin.

3. Add the .jar to your project

Create a lib folder in your project, copy the preverified .jar file in there. Right-click on your project, go to “Properties” → “Java Build Path” → “Libraries” tab → “Add JARs”, select the preverified .jar file that you've just copied in the lib folder, click and “OK”. Now, while in “Properties” window, go to “Order and Export” tab and check the newly added .jar. Click “OK” and you should be done.

BlackBerry Simulator - Enable Internet Access


Testing your BlackBerry device application that connects to the internet obviously requires your simulator to initiate a connection. This can be done very easy from Eclipse IDE. Here's what you can do. There are 2 ways:

1. From Properties

Right-click on your project, go to "Properties" -> "Run/Debug Settings", click on your application from the list and click "Edit". Go to the "Simulator" tab, check the "Launch Mobile Data System Connection Service (MDS-CS) with simulator" box and click "OK".

2. From Eclipse IDE folder

Go to %ECLIPSE_DIR%\plugins\net.rim.ejde.componentpack5.0.0_5.0.0.25\components\MDS\ and launch run.bat.

For this case, remember to launch run.bat before you start the simulator.

General Note

The internet access is not global so it can be enabled only for individual projects.

BlackBerry Screen Transition Sample Code


Screen Transition Sample

The code that I'm about the share and go through illustrates a slide and a fade transition. When the user opens the application, the first screen appears on the BlackBerry® device and displays a button. When the user clicks the button, a second screen slides onto the display from the right. The second screen automatically fades off of the display after two seconds.

First, let's add the imports:

import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.decor.*;

Now, let's continue with the application:

public class ScreenTransitionSample extends UiApplication implements FieldChangeListener {
    private Screen secondaryScreen;
    private Runnable popRunnable;

As you can see, we initialized two local variables. As the official documentation says, you must explicitly initialize local variables in a method. We've initialized the secondaryScreen variable, because Screen is the base class for all screens and popRunnable. The Runnable interface should be implemented by any class whose instances are intended to be executed by a thread. The class must define a method of no arguments called run. We'll implement run later in the code.

Any BlackBerry application that provides a user interface must extend the UiApplication class. Our class also implements FieldChangeListener. This is because Classes that want to handle change events should implement this interface.

public static void main(String[] args) {
        ScreenTransitionSample theApp = new ScreenTransitionSample ();
        theApp.enterEventDispatcher();
    }

Class ScreenTransitionSample must have one method: main(String[] args) which is the entry point into our application. We need to create an instance of our application and start it by getting it on the event thread. Next, we will create the secondary screen:

public ScreenTransitionSample () {
    secondaryScreen = new FullScreen();
    secondaryScreen.setBackground(
    BackgroundFactory.createSolidBackground(Color.LIGHTBLUE) );
    LabelField labelField = new LabelField("The screen closes automatically in two seconds by using a fade transition");
    secondaryScreen.add(labelField);

and create the transition object:

TransitionContext transition = new TransitionContext(TransitionContext.TRANSITION_SLIDE);
transition.setIntAttribute(TransitionContext.ATTR_DURATION, 500);
transition.setIntAttribute(TransitionContext.ATTR_DIRECTION, TransitionContext.DIRECTION_RIGHT);
transition.setIntAttribute(TransitionContext.ATTR_STYLE, TransitionContext.STYLE_PUSH);

which helps us create the animation. Next, we create an engine object:

UiEngineInstance engine = Ui.getUiEngineInstance();

As the official documentation says, a UI engine maintains a stack of Screen objects. As it pushes screens onto the stack, it draws them on top of any other screens already on the stack. When the application pops a screen off the stack, it redraws the underlying screens as necessary. Only the screen on the top of the stack receives input events.

Now, apply the transition, create a new one and apply that too onto the secondary screen:

engine.setTransition(null, _secondaryScreen, UiEngineInstance.TRIGGER_PUSH, transition);
transition = new TransitionContext(TransitionContext.TRANSITION_FADE);
transition.setIntAttribute(TransitionContext.ATTR_DURATION, 500);
transition.setIntAttribute(TransitionContext.ATTR_KIND, TransitionContext.KIND_OUT);
engine.setTransition(secondaryScreen, null, UiEngineInstance.TRIGGER_POP, transition);

Now, create the base screen and attach a button:

MainScreen baseScreen = new MainScreen();
baseScreen.setTitle("Screen Transition Sample");
ButtonField buttonField = new ButtonField("View Transition", ButtonField.CONSUME_CLICK) ;
buttonField.setChangeListener(this);
baseScreen.add(buttonField);

Push the screen onto the stack and paint it:

pushScreen(baseScreen);

then continue with the run method that we discussed about at the beggining and the rest of the code:

popRunnable = new Runnable() {
        public void run() {
            popScreen(secondaryScreen);

here we remove the base screen from the display stack, and updates the secondary screen.

}
        };
    }

    public void fieldChanged(Field field, int context) {
        pushScreen(secondaryScreen);
        invokeLater(popRunnable, 2000, false);
    }
}

We added a listener onto the button and now we took care of the action that needs to be taken on it.

That's about it. Here's the full code sample:

import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.decor.*;

public class ScreenTransitionSample extends UiApplication implements FieldChangeListener {
    private Screen _secondaryScreen;
    private Runnable _popRunnable;
    public static void main(String[] args) {
        ScreenTransitionSample theApp = new ScreenTransitionSample ();
        theApp.enterEventDispatcher();
    }

    public ScreenTransitionSample () {
        secondaryScreen = new FullScreen();
        secondaryScreen.setBackground( BackgroundFactory.createSolidBackground(Color.LIGHTBLUE) );

        LabelField labelField = new LabelField("The screen closes automatically in two seconds by using a fade transition");
        secondaryScreen.add(labelField);
        TransitionContext transition = new TransitionContext(TransitionContext.TRANSITION_SLIDE);
        transition.setIntAttribute(TransitionContext.ATTR_DURATION, 500);
        transition.setIntAttribute(TransitionContext.ATTR_DIRECTION, TransitionContext.DIRECTION_RIGHT);
        transition.setIntAttribute(TransitionContext.ATTR_STYLE, TransitionContext.STYLE_PUSH);

        UiEngineInstance engine = Ui.getUiEngineInstance();
        engine.setTransition(null, _secondaryScreen, UiEngineInstance.TRIGGER_PUSH, transition);

        transition = new TransitionContext(TransitionContext.TRANSITION_FADE);
        transition.setIntAttribute(TransitionContext.ATTR_DURATION, 500);
        transition.setIntAttribute(TransitionContext.ATTR_KIND, TransitionContext.KIND_OUT);
        engine.setTransition(_secondaryScreen, null, UiEngineInstance.TRIGGER_POP, transition);

        MainScreen baseScreen = new MainScreen();
        baseScreen.setTitle("Screen Transition Sample");

        ButtonField buttonField = new ButtonField("View Transition", ButtonField.CONSUME_CLICK) ;
        buttonField.setChangeListener(this);
        baseScreen.add(buttonField);
        pushScreen(baseScreen);
    }
}

popRunnable = new Runnable() {
    public void run() {
        popScreen(_secondaryScreen);
    }
};

public void fieldChanged(Field field, int context) {
    pushScreen(_secondaryScreen);
    invokeLater(_popRunnable, 2000, false);
}

For more examples you can check out the official documentation on http://docs.blackberry.com/en/developers/subcategories/?userType=21&category=Development+Guides

Blackberry environment problems under Ubuntu


In one of my previous entries I presented the necessary steps to set up a working BlackBerry environment under Ubuntu. It does set up the environment, but that is only the beginning. More problems arise.

There's a big problem regarding the editor. It's so buggy that it makes editing virtually impossible. You could of course write your code in another editor and import it in Eclipse but that doesn't sound too elegant, does it.

The best solution, for now, is to set up your environment under Windows on VirtualBox.

Follow these instructions on installing VirtualBox http://www.virtualbox.org/wiki/Linux_Downloads

After the installation, start your machine and follow the steps from this tutorial http://bogdannistor.blogspot.com/2011/01/blackberry-environment-setup-under_17.html

I tested the BlackBerry simulator in VirtualBox on a 2GB of RAM machine and it proved to run quite smooth and, I must say, better than on Ubuntu, on same machine.

BlackBerry Environment Setup Under Windows


Notes:

These instructions were developed under Windows XP

Before proceeding with the setup, create a developer account on http://na.blackberry.com/eng/developers/

Steps:

1.Install Sun JDK. Download the latest version of JDK for Windows from http://www.java.sun.com and install it.

2.Install Eclipse. You need to get the windows 3.5.2 version from here http://www.eclipse.org/downloads/packages/release/galileo/sr2 and extract it.

3.Start Eclipse and go to: Help -> Install New Software and click on Add. In the location field insert this link http://www.blackberry.com/go/eclipseUpdate/3.5/java and click OK. From the list that is generated select “Blackberry Java Plug-in” and “BlackBerry Java SDK 5.0.0” and start the installation. Now you'll use the user/password created on http://na.blackberry.com/eng/developers/. You'll be prompted for those. After eclipse finishes the installations, restart it.

Now you should have a working environment.